persistence-cli
v2.0.0
Published
Stop AI hallucinations. One scan gives your AI tools ground truth about your project.
Downloads
31
Maintainers
Readme
Persistence
Stop AI from hallucinating about your codebase.
Persistence scans your project — config, schema, routes, conventions, utilities, and team decisions — and generates a compact ground truth document that AI coding tools actually read. No more wrong imports, phantom functions, made-up API routes, or reinvented helpers.
Quick Start
npx persistence-cli scanThat's it. Your AI tools now know the truth about your project.
What v2.0 Adds
Persistence has grown beyond just tech facts. It now captures the unwritten parts of a codebase that AI tools fabricate:
- Routes — Next.js App Router (
route.ts) and Express endpoints, mapped automatically - Conventions — empirically detected style rules: import paths, HTTP client, validation library, error handling, naming, DB access layer, component style
- Utilities — exports from
lib/,utils/,helpers/,common/,shared/, ranked by usage count so AI reuses them instead of recreating them - Decision Ledger — a team-shared
.persistence/decisions.yamlof rules added viapersistence note. Committed to git, picked up live bypersistence watch - Feedback —
persistence feedbackopens a 10-second interactive prompt and prints a copy-paste block for GitHub issues
All of this still stays under ~800 tokens for typical projects.
What It Does
Persistence walks your project, identifies relevant files by content fingerprint (not hardcoded paths), and extracts:
| What | Where it finds it |
|------|-------------------|
| Tech Stack | package.json — frameworks, ORMs, key libraries |
| Database Schema | Prisma schemas, Drizzle definitions, or SQL migrations — anywhere in your project |
| API Routes | Next.js route.ts files and Express app.get/router.post calls |
| Environment Variables | .env* files — variable names only, never values |
| TypeScript Config | Any *.json with compilerOptions (not just tsconfig.json) |
| Path Aliases | tsconfig.json / jsconfig.json |
| Project Structure | src/ or app/ directory tree |
| Scripts | package.json scripts |
| Code Conventions | 9 patterns counted across .ts/.tsx/.js/.jsx (imports, HTTP, validation, state, ORM, errors, components, naming, DB layer) |
| Shared Utilities | Every export in lib//utils//helpers//common//shared/, with import counts |
| Team Rules | .persistence/decisions.yaml — added via persistence note |
Fingerprint-based discovery means it works even if your schema.prisma lives in db/ instead of prisma/, your tsconfig is named tsconfig.app.json, or you have both .env.example and .env.staging.
What It Generates
One compact markdown payload, written to:
| File | Purpose |
|------|---------|
| PERSISTENCE.md | Standalone ground truth document |
| .cursorrules | Cursor AI rules |
| CLAUDE.md | Claude Code context |
| .github/copilot-instructions.md | GitHub Copilot instructions |
| .windsurfrules | Windsurf AI rules |
Existing rules in those files are preserved below a clear separator:
# --- User Rules Below ---Re-running persistence scan never duplicates content — it replaces the generated block and keeps everything below the separator intact.
Example Output
A typical PERSISTENCE.md for a Next.js + Prisma app (~345 tokens):
# PERSISTENCE fintech-app | 2026-05-08
## Stack
next@^14.2.1, react@^18.2.0, @prisma/client@^5.10.2, next-auth@^4.24.0, stripe@^14.12.0, zod@^3.22.4, vitest@^1.2.0
DB: prisma
Auth: NextAuth
UI: Next.js + Tailwind (src)
Test: Vitest
## Schema
User: id(String PK), email(String UQ), name(String?), role(String)
Account: id(String PK), userId(String), user(User FK→User), balance(Int), status(String)
## Routes
GET /api/accounts/:id → app/api/accounts/[id]/route.ts
GET /api/users → app/api/users/route.ts
POST /api/users → app/api/users/route.ts
## Env
DATABASE_URL, NEXTAUTH_SECRET, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, NEXT_PUBLIC_APP_URL
## Paths
@/* → ./src/*
## Structure
src/ (10 files)
src/components/ (5 files)
src/lib/ (3 files)
src/services/ (2 files)
## Scripts
dev → next dev
build → next build
test → vitest run
## Conventions
imports: @/ aliases (100%, 7 of 7 files)
errors: try-catch (100%, 5 of 5 files)
naming: camelCase for functions (71%, 20 of 28 files)
## Utilities (use these, don't recreate)
cn(...classes: string[]), slugify(text: string), truncate(text: string, n: number) → src/lib/strings.ts
formatCurrency(amount: number), parseCurrency(str: string) → src/lib/currency.ts
getSession(), requireAuth(), validateToken(token: string) → src/lib/auth.ts
## Rules
After schema changes, run prisma generate
Use fetch, never axiosAbbreviations: PK primary key, UQ unique, FK→X foreign key to table X, ? nullable, [] array.
Every scan ends with an estimated token count so you can see what's landing in the AI's context window.
Supported AI Tools
- Cursor —
.cursorrules - Claude Code —
CLAUDE.md - GitHub Copilot —
.github/copilot-instructions.md - Windsurf —
.windsurfrules - Aider — use
PERSISTENCE.mddirectly with/read PERSISTENCE.md
CLI Reference
persistence scan
Scan the project and inject ground truth into AI config files.
persistence scan # Default: cursor + claude
persistence scan --targets cursor,claude,copilot,windsurf # All targets
persistence scan --dry-run # Preview without writing
persistence scan --quiet # No UI, print written file paths| Flag | Description |
|------|-------------|
| --targets <list> | Comma-separated: cursor, claude, copilot, windsurf. Default: cursor,claude |
| --dry-run | Print output without writing any files |
| --quiet | Suppress the animated UI; print one written file path per line |
persistence init
First-time setup. Runs scan and adds the four cache files (conventions.json, symbols.json, meta.json, feedback-log.json) plus PERSISTENCE.md to .gitignore. Note: .persistence/decisions.yaml is intentionally not gitignored — it's your shared team rule file.
persistence init
persistence init --targets cursor,claude,copilot,windsurfpersistence watch
Watch for file changes and re-run scan automatically.
persistence watchTriggers on changes to package.json, tsconfig.json, prisma/schema.prisma, .env*, src/**/*, app/**/*, and .persistence/decisions.yaml. When the ledger changes (you add a rule, or a teammate's rule comes in via git pull), the scan auto-runs and prints 📋 Decision ledger updated — ledger reloaded.
persistence clean
Remove all Persistence-generated content.
persistence clean
persistence clean --targets cursor,claudeFor each target file:
- Fully-generated files are deleted.
- Files with user content below
# --- User Rules Below ---are restored to just that user content.
persistence conventions
Print a detailed report of detected code conventions, including ones below the 70% confidence threshold that don't make it into the markdown.
persistence conventionsDetected Conventions:
┏━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Name ┃ Pattern ┃ Confidence ┃ Count ┃
┣━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━╋━━━━━━━━━━━┫
┃ Import Style ┃ @/ aliases ┃ 100% ┃ 7/7 ┃
┃ Error Handling ┃ try-catch ┃ 100% ┃ 5/5 ┃
┃ Naming ┃ camelCase for functions ┃ 71% ┃ 20/28 ┃
┗━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━┻━━━━━━━━━━━┛Persistence detects 9 conventions: imports, HTTP client, validation library, state management, ORM usage, error handling, component style, naming, and DB access layer. A convention is shown in the markdown only when it has ≥70% confidence and ≥5 files of evidence.
persistence utils
Show the full utility index — every export from lib/, utils/, helpers/, common/, shared/, with import counts so you know which utilities are load-bearing.
persistence utilsShared Utilities (9 exports in 2 files):
src/lib/auth.ts (3 exports, used by 22 files):
getSession() → 22 imports
requireAuth() → 18 imports
validateToken(token:string) → 9 imports
src/lib/currency.ts (2 exports, used by 14 files):
formatCurrency(amount:number) → 14 imports
parseCurrency(str:string) → 6 importsThe top utilities (by usage) automatically appear in PERSISTENCE.md under ## Utilities (use these, don't recreate).
persistence note
Add a team rule to the decision ledger. The ledger lives at .persistence/decisions.yaml and is meant to be committed to git so the whole team shares the same AI guidance.
persistence note "Use fetch, never axios. We removed axios in March."
persistence note "DB queries through service layer only" --scope "src/app/api/*"--scope defaults to * (global, always included). A scoped rule is only included in the AI context when files matching the glob exist in the project.
The ledger is YAML with comments, and looks like:
# Persistence Decision Ledger
# These rules are injected into AI tools for your entire team
# Add rules with: persistence note "your rule here"
# This file should be committed to git
- id: 1746736800000
date: '2026-05-08'
rule: Use fetch, never axios. We removed axios in March.
scope: '*'
author: Nitish SM
source: manualAuthor is auto-detected from git config user.name. After adding a rule, you can run persistence scan to update the AI context immediately — or just let persistence watch pick it up.
persistence notes
List all decisions in the ledger.
persistence notesDecision Ledger (2 entries):
#1 2026-05-08 [global] Use fetch, never axios.
id: 1746736800000 · by Nitish SM
#2 2026-05-08 [src/app/api/*] DB queries through service layer only
id: 1746736900000 · by Nitish SMRemove a rule with persistence notes remove <id> — it'll ask for (y/n) confirmation.
persistence feedback
Open an interactive feedback prompt. Pick a category, type a one-line description, and Persistence prints a copy-paste block (with system context) you can drop into a GitHub issue.
persistence feedback┌──────────────────────────────────────────────┐
│ Persistence Feedback │
└──────────────────────────────────────────────┘
How's Persistence working for you?
1. 🐛 Something broke
2. 🤔 AI still hallucinates about something
3. 💡 Feature request
4. ❤️ It's working great
5. 📝 Other feedbackPersistence also nudges you exactly three times — on your 3rd, 7th, and 14th scan — with a subtle one-line hint to share feedback. After that, it's silent.
Before & After
Without Persistence
You: "Add a new API route for orders"
AI: import { db } from '@/lib/database' ← wrong path
import { OrderModel } from '@/models' ← doesn't exist
export default function handler(req, res) { ← wrong pattern (you use App Router)
const result = await axios.post(...) ← team removed axios 6 months ago
const helper = (x) => slugify(x) ← reinvented; cn() already exists in src/lib/strings.tsWith Persistence
You: "Add a new API route for orders"
AI: import { db } from '@/db/client' ← correct path (from ## Paths)
import { Order } from '@prisma/client' ← real type (from ## Schema)
import { slugify } from '@/lib/strings' ← reused from ## Utilities
export async function GET(request: Request) { ← correct App Router pattern (from ## Routes)
const result = await fetch('/api/...') ← matches team rule (## Rules: "Use fetch, never axios")How It Works
- Discover — Walk the project, skip
node_modules/.git/dist/etc, and match files by content fingerprint (not filename). - Select — For each parser: pick root-most
package.json/.env, prefer exacttsconfig.json, merge all discovered schemas, collect all route files. - Parse — Extract stack, schema, routes, env vars, aliases, structure, scripts.
- Analyze — Walk source files once, then detect conventions and index utility exports + usage counts in a single pass.
- Load ledger — Read
.persistence/decisions.yaml, filter scoped rules to files that exist. - Generate — Produce a compact, token-efficient markdown summary with per-section token caps.
- Inject — Prepend into AI tool config files, preserve any existing user rules below
# --- User Rules Below ---.
Re-run persistence scan whenever your project changes — or leave persistence watch running and forget about it.
Token Budget
AI tools have finite context windows. Persistence enforces per-section caps to stay small:
| Section | Cap (≈ tokens) | |---|---| | Stack | 200 | | Schema | 250 | | Routes | 100 | | Env | 80 | | Paths | 50 | | Structure | 100 | | Scripts | 80 | | Conventions | 150 | | Utilities | 150 | | Rules | 150 |
When a section would exceed its cap, the lowest-priority entries are dropped (lowest-confidence conventions first, least-used utility groups first, oldest rules first). The scan ends with Context: ~N tokens (target: under 1000) so you always know where you stand.
Other ways the output stays small:
- No HTML comments, no bold markdown, no bulleted lists.
- Dependencies on one comma-separated line (not a 50-row table).
- Schema tables inline: one line per table, all columns together.
- Dev dependencies dropped unless they're frameworks (vitest, jest, playwright, eslint).
- Empty sections omitted entirely.
- Structure truncated to directories with file counts, 2 levels max.
A typical output is 200–500 tokens.
File Layout
After persistence init, your project gets:
.persistence/
decisions.yaml ← team rules, COMMITTED to git
conventions.json ← cache, gitignored
symbols.json ← utility index cache, gitignored
meta.json ← scan count + last-run state, gitignored
feedback-log.json ← local feedback history, gitignored
PERSISTENCE.md ← gitignored (regenerable)
.cursorrules ← AI config, your call whether to commit
CLAUDE.md ← AI config, your call whether to commitContributing
Contributions welcome:
- More language ecosystems — Cargo.toml, go.mod, requirements.txt, Gemfile, composer.json (fingerprints already defined; parsers pending)
- Middleware + auth parser — fingerprints exist, parsers pending
- New AI tool targets — Aider native config, Continue.dev, etc.
.persistence/config.yaml— user override for include/exclude globs- Convention detectors — more patterns to catch
git clone https://github.com/nitishsm2002/persistence-cli.git
cd persistence-cli
npm install
npm run build
npm link # Test locally with `persistence scan`License
MIT
