vibe-replay
AGENTS.md39 starsChanged 3 months agoMITView on GitHub
This is the single source of truth for agent instructions in this repo. Every coding agent reads it, directly or through a shim — see Agent setup at the bottom. Put project knowledge here, not in a tool-specific file. vibe-replay turns AI coding sessions into animated, interactive, self-contained HTML replays. Supports Claude (Code/Desktop/Cowork), Cursor, Codex, OpenCode, Hermes, Muse, Pi, and Grok Bot. AI Studio embeds Pi's provider registry and agent loop; no separate AI CLI is required. pnpm monorepo: packages/cli…
- Reads credentials
- Installs packages
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,439 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository3 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- .cursor/rules/project-instructions.mdcCursor rule
- replaySkill
The file
Show its rules and text
We split this AGENTS.md into the rules it gives an agent
15 rules. About 0 are common, 7 are distinctive, and 8 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈7
Distinctive · estimate
This author's own choices. Worth a look.
8
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 32
When to use which:
- Very specific: line 50
All tables (replays + Better Auth auth tables) are defined in cloudflare/src/db/schema.ts
- Very specific: line 59
lastIndexOf("</head>"): Use lastIndexOf, not indexOf — minified JS in the viewer bundle may contain the string </head> - Very specific: line 104
Before commit: run pnpm lint:check and fix any errors or warnings (it uses --deny-warnings).
- Very specific: line 105
Before PR: run pnpm verify. Keep its stages sequential.
- Distinctive: line 108
After changes: update AGENTS.md / README.md / CONTRIBUTING.md if anything becomes outdated.
- Distinctive: line 112
Test with both small (~30 scenes) and large (~500 scenes) sessions
- Distinctive: line 117
When creating a release for npm/GitHub, do this in order:
- Distinctive: line 122
Verify displayed CLI version matches package version:
- Very specific: line 125
Only then create tag/release/publish for that same version.
- Very specific: line 127
If tag/release is updated but packages/cli/package.json is not, CLI will still show the old version.
- Distinctive: line 165
Every agent reads this file.
- Very specific: line 194
Keep AGENTS.md under 32 KiB — Codex's default project_doc_max_bytes.
- Very specific: line 214
Create it at skills/<name>/SKILL.md, then link both discovery locations:
- Distinctive: line 221
Never copy a skill into a discovery directory — the two locations drift silently and nothing catches it until an agent acts on a stale …
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
AGENTS.md — vibe-replay
This is the single source of truth for agent instructions in this repo. Every coding agent reads it, directly or through a shim — see Agent setup at the bottom. Put project knowledge here, not in a tool-specific file.
What is this
vibe-replay turns AI coding sessions into animated, interactive, self-contained HTML replays. Supports Claude (Code/Desktop/Cowork), Cursor, Codex, OpenCode, Hermes, Muse, Pi, and Grok Bot. AI Studio embeds Pi's provider registry and agent loop; no separate AI CLI is required.
pnpm monorepo: packages/cli (npm: vibe-replay), packages/viewer (React → single HTML), packages/types (shared types), website/ (Astro), cloudflare/ (Workers API).
Commands
pnpm install # Install deps
pnpm build # Full build: viewer → cli
pnpm start # Build + run interactive picker
pnpm dev # Viewer (Vite HMR) + CLI (tsx + Node fs.watch restarts) together
pnpm dev:dashboard # Dev mode with dashboard flag (-d)
pnpm dev:website # Website (Astro HMR) + Viewer (Vite HMR) together
pnpm test # Run unit tests
pnpm test:cloudflare # Run Cloudflare worker tests
pnpm test:e2e # Run E2E tests (requires pnpm build first)
pnpm lint # Lint + format (auto-fix)
pnpm lint:check # Lint check (no fix, for CI)
pnpm typecheck # Strict TypeScript + website checks
pnpm verify # Sequential pre-PR gate: lint, types, tests, buildWhen to use which:
pnpm start— validate full user flow (build + run)pnpm dev— daily iteration with full HMR: viewer auto-reloads via Vite, and the CLI auto-restarts through the launcher's Nodefs.watchwatcher rather thantsx watch(which would intercept interactive stdin)pnpm dev:website— website + viewer iteration: Astro HMR +/view/redirects to Vite viewer- Dev launchers reserve ports for their entire lifetime. If one component's port is explicitly overridden, automatic selection skips that port for the other component as well. Use
VIBE_API_PORT/VIBE_VIEWER_PORTforpnpm devandVIBE_VIEWER_PORT/VIBE_WEBSITE_PORTforpnpm dev:website.
Database (D1 + Drizzle)
Schema is managed by Drizzle ORM — single source of truth in cloudflare/src/db/schema.ts.
When you change the schema:
cd cloudflare
pnpm db:generate # Drizzle reads schema.ts, generates SQL migration in drizzle/
pnpm db:migrate:local # Apply to local D1
pnpm db:migrate:remote # Apply to production D1 (requires auth)- All tables (replays + Better Auth auth tables) are defined in
cloudflare/src/db/schema.ts - Migration files live in
cloudflare/drizzle/— commit them to git - Never hand-edit migration files — always use
drizzle-kit generate schema.sqlis kept as a reference but migrations are the source of truth- Better Auth uses
drizzleAdapter— it reads/writes auth tables through the same Drizzle instance
Gotchas
</escaping: JSON in<script>tags MUST escape</as<\/— browsers close the tag otherwise (seegenerator.ts)lastIndexOf("</head>"): UselastIndexOf, notindexOf— minified JS in the viewer bundle may contain the string</head>- Shared types:
Scene,Annotation,DataSourceInfo,ReplaySessionlive inpackages/types(@vibe-replay/types). CLI and viewer re-export from there. Provider-specific and viewer-specific types remain in their respective packages. - Viewer bundle: The viewer ships as a self-contained HTML artifact. There is no hard size cap; keep an eye on bundle size and runtime performance as features grow.
- Viewer styling: Tailwind v4 via the
@tailwindcss/viteplugin (nopostcss.config/autoprefixer). The terminal theme lives inpackages/viewer/tailwind.config.js, loaded fromsrc/styles/index.cssvia@config(v4's legacy-config bridge). The website is also on v4. - Self-contained HTML: Output must make zero automatic external requests. Viewer assets are inlined; remote replay images follow the explicit-consent rule below.
- Remote replay images:
ReplayImagerendersdata:image/*sources immediately, but HTTP(S) images require an explicit per-image click and useno-referrer. Never attach an external URL to<img src>before consent. - Multi-file sessions: Claude Code
/resumecreates new JSONL files. Parser acceptsstring | string[]and merges by slug+project. - Cursor tri-source: Sessions come from SQLite
store.db(primary),globalStorage/state.vscdb, or JSONL (fallback). Discovery merges all sources. DB data is source of truth; JSONL supplements missing thinking/images. - Cursor SDK (discovery perf: see
packages/provider-cursor/AGENTS.md): SDK agents (TypeScript@cursor/sdk) write to~/.cursor/projects/<workspace>/sdk-agent-store/<projectHash>/index.db(tables:agents,runs,run_events) and a parallel JSONL transcript atagent-transcripts/<agentId>/<agentId>.jsonl. The transcript is the source of user prompts (SDK doesn't store them in events) and the SDK index.db supplies tool results, structured per-run timing, and per-turn model. Seepackages/provider-cursor/src/cursor/sdk-reader.ts. Detection is by sessionId prefixagent-— IDE chat sessions (UUID-only) skip the SDK SQLite probe. Newer stores addruns.usage_json(camelCase:inputTokens,outputTokens,cacheReadTokens,cacheWriteTokens) and it is the only token source for SDK sessions; optional columns are probed withPRAGMA table_infoso older stores still parse. Tool duration comes from the first/lastrun_events.created_atof a call. Tool enrichment pairs positionally but validates the tool name (Edit/Write/MultiEdit count as one family) and prefers a matching path/command, leaving a block unenriched rather than attaching another tool's result. - Cursor subagents: IDE delegation trajectories live beside the parent transcript at
agent-transcripts/<sessionId>/subagents/<subAgentId>.jsonl. ParentTask/Subagentcalls do not carry the file ID, sopackages/provider-cursor/src/cursor/parser.tslinks them by normalized delegated-prompt containment and intentionally leaves unmatched files unattached rather than guessing by completion order. Current files contain tool calls and assistant text/reasoning, but usually no tool IDs/results, timestamps, token usage, or actual subagent model. - Cursor structured subagents: Global-state
composer.composerHeaders(stored inItemTable) may identify child composers throughsubagentInfo.parentComposerId/toolCallId. Discovery hides those children from the top-level session list, and parsing links them to the exact parentAgenttool call. Structured IDs take precedence; transcript prompt containment remains the legacy fallback. - Cursor project paths:
~/.cursor/projects/<encoded>encodes the workspace path by replacing/with-and dropping each segment's leading dot, so~/.cursorarrives asUsers-me-cursor.decodeProjectDirresolves the ambiguity by walking the real filesystem, trying<candidate>then.<candidate>at each level. The walk now returns the deepest directory it could confirm: once a real parent is found, everything left over is kept as one segment rather than split on-, because these workspaces are routinely deleted and splitting shredded their run ids (a scratch dir ending in a UUID became five path segments and a project named after the UUID's tail). Only when nothing below the root resolves does it fall back to replacing every-with/. - Agent run workspaces: Automation creates one scratch workspace per run (Cursor SDK artifacts, PR-review worktrees,
/var/folderstemp dirs), each its own project.agentRunWorkspaceParentindashboard-utils.tsspots a run id at the end of a directory name — a UUID or a hex digest of 12+ chars, short enough digests are left alone soros-4and PR numbers don't match — and rolls the session up under the directory that holds it.rollupProjectapplies that alongside the Claude.claude/worktreesrule, so every surface gets it. Sessions, Replays, and Projects hide these workspaces by default (agentRuns=truein the URL shows them); the filter runs on the raw project path, since the rollup produces a parent that no longer looks like a run workspace. - Project identity:
packages/types/src/project-identity.tsis the shared classification layer for raw workspace paths. It preserves the generic run-id safeguards, recognizes Cursor SDK automation roots/workflows (including hyphen/underscore path variants), carries repository/PR metadata, and exposes canonical keys consumed by scanner aggregation and viewer rollups. Raw top-project entries remain available so the ProjectsagentRuns=truetoggle can reveal individual workspaces. - Cursor duplicate transcripts: Discovery coalesces duplicate transcript copies by session ID without adding their byte counts, and parsing removes identical records only across distinct files. Inline tool blocks are authoritative; mtime sidecars may fill unresolved tools but must not create duplicate calls.
- Claude Cowork replay duplicates: Cowork
audit.jsonlcan contain a host-loop user record plus anisReplay: truecopy of the same prompt. Discovery excludes replay copies that match an original by content/UUID, and rich scans use the Cowork parser so dashboard prompt analytics match replay output. - Cowork
resultbilling: eachtype: "result"record is one completed host-loop run's final bill —usage,modelUsage,total_cost_usdandduration_msare per-run, not cumulative. Session totals are the sum over UUID-deduplicated results; never mix them with assistantmessage.usagesnapshots (those are partial streams and undercount output badly), and never addusagetomodelUsageortotal_cost_usdtomodelUsage.*.costUSD. Audits with noresultrecords fall back to assistant snapshots plus the timestamp-gap duration estimate.system/api_retrymaps toapiErrorswith attempt metadata only — the raw error text is never stored. - Provider-reported cost:
ProviderParseResult.reportedCostUsdwins over the local pricing table intransform.ts, which is what makes cost available for model generationspricing.tsdoes not know. - AI Studio runtime:
packages/cli/src/ai-runtime.tsowns the embedded PiModelsregistry, provider-owned API-key/OAuth flows, and atomic 0600 auth/settings files.feedback.tsruns Coach, Translate, and Tone through@earendil-works/pi-agent-corewith a single structured result tool and no filesystem, network, or MCP tools. The initial registry contains OpenAI API key, ChatGPT plan OAuth, Codex OAuth, OpenRouter API key/OAuth, and OpenCode Zen API key providers. The editor can also configure one OpenAI-compatible proxy (custom-openai), discover models from/modelsor/model, and send Chat Completions requests through Pi. Shared provider settings live inpackages/viewer/src/components/AiProviderSettings.tsxand render the same provider/model/auth/custom-endpoint controls inline in Settings or inside the AI Studio modal; model choices are searchable and the selection is remembered in browser storage and synced to~/.vibe-replay/ai-settings.json. Vibe Replay prefersgpt-5.6-lunaand never reads Pi's default. Endpoint metadata lives in a separate atomic 0600~/.vibe-replay/ai-providers.jsonfile (VIBE_REPLAY_AI_CONFIGcan override it); custom API keys remain in the credential store and are never returned by provider discovery. HTTP custom endpoints are limited to loopback; remote endpoints must use HTTPS. Provider setup is intentionally separate from replay/session credentials. - opencode: SQLite discovery and parsing support legacy and v2 stores. See
packages/provider-opencode/AGENTS.md. - Hermes: Sessions live in SQLite at
~/.hermes/state.db(FTS5-backed), plus one DB per named profile at~/.hermes/profiles/<name>/state.db. Discovery scans all of them (hermesDbPaths()inpackages/provider-hermes/src/hermes/sqlite.ts), dedups by session id, and writes<dbPath>#session:<id>marker paths; parse resolves the right DB from the marker before falling back to a scan.hermesRootDir()mirrors Hermes'sget_default_hermes_root():HERMES_HOMEinside~/.hermesis profile mode (root stays~/.hermes); outside it, that path itself is the root. Caveat: sql.js reads raw file bytes and cannot replay un-checkpointed-walframes, so sessions still sitting in the WAL (long-running gateway) are invisible until Hermes checkpoints. Tables:sessions(token/cost/git columns maintained by Hermes) +messages(OpenAI-style: assistant rows carrytool_callsJSON +reasoning; tool rows carrytool_name+tool_call_id+ result content). Discovery writes<dbPath>#session:<id>marker paths and reads~/.hermes/.update_checkfor the version. Context compaction is recorded two ways:compacted=1rows (pre-compaction history, kept in full) and a user row prefixed[CONTEXT COMPACTION(→subtype: "compaction-summary", mirroring claude-code); everycompacted=1run boundary emits its own compaction event, summary-only stores fall back to marker-derived compaction metadata, and discovery excludes those marker rows from prompt counts and firstPrompt. Provider cost:reportedCostUsdcomes fromsessions.actual_cost_usd, falling back toestimated_cost_usdwhen positive (cost_status"included"/"unknown" store 0). Parse is SQLite-backed, so background scan usesbuildLightweightHermesScanResultinscanner.ts. Tool names map to the viewer vocabulary intool-mapping.ts. Bot Mode: each Bot is a profile (~/.hermes/profiles/<bot>/), its canonical Bot Chat is ahidden=1session (title: "Bot Chat",profile_name: "<bot>",cwd: "") with the same storage shape; discovery treatscwd-less sessions as~/.hermes/profiles/<bot>so bots appear as distinct projects, and parse infers per-tool*_durationMsfromassistant.timestamp → tool.timestampso activity timelines no longer show large unattributed gaps. Seepackages/provider-hermes/. - Grok Bot: Cloud-box JSONL at
/home/box/agent-data/agent-transcripts(andsand-data); envGROK_BOT_TRANSCRIPTS_DIR/VIBE_REPLAY_GROK_BOT_DIR; optional~/.grok-bot/agent-transcripts. Macsand-client-persistencereplicas are in scope. Details:packages/provider-grok-bot/AGENTS.md. - Skip
progresslines: These are subagent streaming artifacts in JSONL. - sql.js (WASM): Used instead of native SQLite bindings for portability — no C++ compiler needed.
- Session discovery cache: CLI picker + local dashboard use file cache at
~/.vibe-replay/cache/*.json(stale-while-refresh UX). Cache validity is tied to CLI release version (CLI_VERSION) plus envelope version, so caches auto-invalidate across releases. Home cold starts use full-screen discovery until the source catalog and replay list are ready; cached source snapshots bypass it, while replay-only caches do not./api/sources/streamprovides early provider storage detection, per-provider reading states, bounded recent-session previews, and preparation counts; local provider reads run concurrently, while final output keeps registry order and session observers run sequentially; shared discovery replays provider states to late SSE subscribers; rich scans continue in the background. Keep cache writes best-effort and never block generation/parsing on cache failures. - Replay compaction snapshots: Generated replay cards must use only the replay summary's own
compactionCount; a missing legacy field means zero. Never fall back to the current source scan, or a replay generated before a later source compaction is mislabeled. - SSH sources:
~/.vibe-replay/config.jsonmay define generic OpenSSH targets underremoteSources.packages/cli/src/remote.tsindexes remote Codex, Claude Code, and Pi JSONL files with the systemsshclient, stages them in a per-target read-only cache through manifest-sized bounded streams, and reuses the existing providers. Leftover.sync-*dirs and 30-day-idle unconfigured target caches are pruned. SSH credentials stay in the user's OpenSSH configuration/agent; do not add keys or passwords to the vibe-replay config. Remote Codex/resumetitles prefer the latest explicitthread_namefromsession_index.jsonl, then fall back to a read-only query ofstate_5.sqlite; the live database and WAL are never copied, and Pythonsqlite3plus a schema-awaresqlite3CLI fallback are supported. Metadata-only, missing, or unreadable sources remain visible withno-promptsorunreadablestatus and cannot generate an empty replay. Remote repository identity is local dashboard metadata and is stripped from share/export payloads. Remote Live mode is disabled. Remote source identity is carried separately from provider/data-source identity so equal session IDs on different targets do not merge. - Dashboard Settings: The editor dashboard's Settings tab manages the same
remoteSourcesentries through local/api/settingsroutes. It validates ids, hosts, providers, and timeouts, writes the config atomically while preserving unrelated keys, and offers a bounded SSH probe. Never add passwords or private keys to this UI or config. - SSH insight privacy: Remote sessions may contribute to local dashboard insights, but
aggregateDailyInsightsexcludes SSH locations from the optional cloud sync so remote project/provider aggregates stay on the machine. - Windows support: Claude Desktop and Cowork search standard
%APPDATA%/%LOCALAPPDATA%roots plus MSIX package storage under%LOCALAPPDATA%\Packages\Claude_*\LocalCache\Roaming; Claude project-memory lookup encodes drive paths asC--Users-...; Cursor encodes workspace dirs asC:\a\b→C-a-b(drive colon dropped, separators →-);decodeProjectDirhas awin32branch that resolves these against the real filesystem (POSIX uses/root, Windows uses the drive root). Cursor on Windows stores IDE chats only in theglobalStorage/state.vscdbunder%APPDATA%\Cursor(there is no~/.cursor/chatsdir). Replay output normalizes file paths to/for cross-platform display viaredactFilePathintransform.ts— never apply that to prose. Build scripts shell out toscripts/copy-file.mjsinstead ofmkdir -p/cp(not available in PowerShell)..gitattributesforceseol=lfso Windows clones don't tripoxfmt --checkwith CRLF. - Pi harness tools (discovery perf: see
packages/provider-pi/AGENTS.md): Pi sessions use both native tool names (bash,edit,write) and harness names (exec_command,apply_patch). The Pi provider mapsexec_commandto replayBashand parsesapply_patchinto replayEditinputs (including all touchedfile_paths, with the first file represented by the single-diff viewer). Harness tools report failure throughdetails.exit_coderather than theisErrorflag native tools use, so a non-zero exit code marks the result as an error. Nativeeditcan carry several replacements; all of them are joined into the singleold_string/new_stringdiff the viewer renders, and the tool stays namedEdit. - Pi token accounting:
compactionandbranch_summaryentries carry the usage of a separate summarization call, so theirusageadds to session totals (matching Pi's own billed totals).tokensBeforeis context size, not usage, and only feedscompactions[].preTokens. Summary usage never entersturnStats. Sessionmodelis the last selected model, which is what discovery reports. - Pi compaction diagnostics:
packages/provider-pi/src/pi/parser.tspreserves successfulcompactionentries and emits privacy-safediagnosticsevents. A successful compaction is labeledautomatic-contextonly as an inferred classification when durable evidence (such as a preceding assistantstopReason: "length") supports it; Pi v3 JSONL does not persist thecompaction_start/compaction_endorsession_compact_failedlifecycle events, so missing failure events are inconclusive. Explicit persisted messages such asAuto-compaction failed: ...become failed compaction diagnostics, while unrelatedstopReason: "error"assistant messages remainassistant-api-errorevents.get_compaction_diagnosticsinpackages/cli/src/local-assistant.tsand the replayStatsPanelexpose these distinctions without exposing raw error text in metadata. - Pi replay metric scope: Assistant records with usage but no visible content remain in aggregate token/cost totals and are reported as a data-quality note, while still producing no empty replay scene. Viewer per-turn joins use
TurnStat.turnIndex; sparse rows remain no-data rather than shifting later prompts. Context charts use the configuredmeta.contextLimitwhen available and distinguish recorded compactions from inferred context drops; the chart's prompt-footprint values are provider-reported accounting, not guaranteed actual model context size. - Usage index (tool / MCP / skill):
scanner.tsderives oneUsageEventper invocation (name, turn, timestamp, duration, status, subagent) plus a per-sessionSessionUsageSummary; only the latest 100 detail events are retained, and tool inputs/results are never stored. MCP naming differs per provider and is normalized inparseMcpUsage:mcp__<server>__<tool>(Claude/Codex),CallMcpTool {server, toolName}(Cursor SDK),mcp-<server>-<tool>(Cursor IDE — split at the last dash because server IDs are kebab-case, and the normalized input'sserver/tool_namewins),mcp_<server>_<tool>(older Cursor — split at the first underscore because tool names are snake_case, withmcp_auth/mcp_get_tools/mcp_meta_tool*excluded as MCP management tools), and Pi's singlemcptool (serverfield, or<server>_<tool>intool). An MCP call is counted undermcpServers/mcpToolsonly — never also as a tool — so the Tool facet lists real tools instead of repeating the MCP facets. Cowork names servers by UUID, so its parser exposesmcpServerNamesfrom the siblinglocal_{id}.jsonremoteMcpServersConfig. OpenCode/Hermes and deferred Cursor scans emit no usage events — they say so indataQualityNotesrather than looking like zero usage. Cursor reports the same server under several ids (user-<name>,<name>::mcpScope:profile:...:cfg:...);stripCursorServerScopefolds them so one server is one facet./api/scan/resultsstrips events; per-session events come from/api/usage/events, and/api/usage/rollupserves the compact per-session{ startTime, usage }projection the Insights page aggregates client-side (engine/usage-rollup.ts, range-filtered by instant so switching 7d/30d/90d costs no request). The dashboard renders Tool and MCP server facets plus a per-session breakdown (engine/session-usage.ts) built from the summary alone, so expanding a card costs no request; the MCP tool facet is a drilldown that only appears once a server or tool is selected, and long facet lists scroll inside their section so the ones below stay reachable. Context compaction counts are indexed with each scan result (OpenCode/Hermes compute them during lightweight discovery), exposed as a URL-synced dashboard signal filter, and shown directly on session/replay cards. The scan-results cache key carriesSCANNER_VERSION, so a bump can't serve results in the previous shape. Insights renders a "Tools & MCP" card (top tools / MCP servers / MCP tools / skills, each with calls and session reach). Cursor SQLite sessions are scanned twice: a fast pass with rich parsing deferred, then a backgroundbackfillDeferredUsagepass inserver.tsthat indexes their usage and rewrites the scan cache (progress in/api/scan/statusasusageBackfill). - Context composition privacy:
ContextBreakdowncontains aggregate counts, UTF-8 byte sizes, and provider-estimated tokens only—never raw system prompts, tool descriptions, schemas, or conversation text. Cursor reads the latestpromptTokenBreakdownsnapshot; Cowork counts its persisted system prompt and enabled MCP definitions; Codex counts rollout base/developer instructions; Pi counts unique MCP descriptors observed in active-branch tool-result metadata. Byte measurements are not token counts, Cursor snapshots are not cumulative usage, and Pi observations may be incomplete. Keep these distinctions in UI copy and preserve the field through parser → replay/scan → durable local insights. - CLI session workflows: See
packages/cli/AGENTS.mdfor reference resolution, saved snapshots, diagnostic coverage, pagination, and read-only storage recovery.
Rules
- Always use pnpm — never npm/yarn
- TypeScript strict mode, ESM throughout
- oxlint for linting, oxfmt for formatting. The lefthook pre-commit hook runs both on staged files for every agent. Claude Code additionally fixes each file right after editing it; other agents should run
pnpm lintthemselves before finishing. - Before commit: run
pnpm lint:checkand fix any errors or warnings (it uses--deny-warnings). Do NOT commit code that fails lint. - Before PR: run
pnpm verify. Keep its stages sequential; concurrent full checks can cause integration-test timeouts. - Before commit: security review — check for leaked secrets, API keys, tokens, credentials, .env files
- Never bump versions or publish without explicit user confirmation
- After changes: update AGENTS.md / README.md / CONTRIBUTING.md if anything becomes outdated. Never edit
CLAUDE.mdto record project knowledge — it is a shim (see Agent setup). - Viewer changes →
pnpm build(rebuilds both packages) - CLI-only changes →
pnpm --filter vibe-replay build - Shared types changes → edit the relevant source under
packages/types/; both CLI and viewer pick the changes up automatically - Test with both small (~30 scenes) and large (~500 scenes) sessions
- Test modification policy — see
packages/cli/test/README.mdbefore changing any test
Release checklist (important)
When creating a release for npm/GitHub, do this in order:
- Confirm with user first (no autonomous publish/version bump).
- Bump
packages/cli/package.jsonversionto the target release version. - Build CLI:
pnpm --filter vibe-replay build. - Verify displayed CLI version matches package version:
node packages/cli/dist/index.js --version- Note: startup banner
vX.Y.Zcomes frompackages/cli/src/version.tsreadingpackages/cli/package.json.
- Only then create tag/release/publish for that same version.
If tag/release is updated but packages/cli/package.json is not, CLI will still show the old version.
Key files
| What | Where |
| CLI entry | packages/cli/src/index.ts |
| Shared types | packages/types/src/index.ts |
| CLI types | packages/cli/src/types.ts |
| Transform (turns → scenes) | packages/cli/src/transform.ts |
| HTML generation | packages/cli/src/generator.ts |
| Editor server | packages/cli/src/server.ts |
| AI provider/runtime | packages/cli/src/ai-runtime.ts / packages/cli/src/feedback.ts |
| Provider interface | packages/provider-contract/src/index.ts (contract) / packages/providers-default/src/index.ts (registry) |
| Cursor SDK reader | packages/provider-cursor/src/cursor/sdk-reader.ts |
| opencode provider | packages/provider-opencode/src/opencode/ |
| Hermes provider | packages/provider-hermes/src/hermes/ |
| Viewer entry | packages/viewer/src/App.tsx |
| Playback engine (pure) | packages/viewer/src/engine/ |
| Playback hook | packages/viewer/src/hooks/usePlayback.ts |
| Session loading | packages/viewer/src/hooks/useSessionLoader.ts |
| View preferences | packages/viewer/src/hooks/useViewPrefs.ts |
| DB schema (all tables) | cloudflare/src/db/schema.ts |
| Auth config | cloudflare/src/auth.ts |
| Worker (Hono routes) | cloudflare/src/worker.ts |
| Drizzle config | cloudflare/drizzle.config.ts |
| Drizzle migrations | cloudflare/drizzle/ |
| E2E test helpers | e2e/helpers.ts |
| E2E: generated HTML | e2e/generated-html.test.ts |
| E2E: editor server | e2e/editor-server.test.ts |
| E2E: CLI smoke | e2e/cli-smoke.test.ts |
| E2E: auth worker | e2e/auth-worker.test.ts |
| Agent instruction wiring | AGENTS.md, CLAUDE.md, .agents/skills/, .cursor/rules/ |
| Ask Replay parity | docs/ai-chat-feature-parity.md |
| Session observability | docs/session-observability-audit.md |
Agent setup
Every agent reads this file. Only the plumbing differs.
| Agent | Instructions | Skills |
| Codex | AGENTS.md (native) | .agents/skills/ (native) |
| Cursor | AGENTS.md (native) + .cursor/rules/ | — |
| Pi | AGENTS.md (native, preferred over CLAUDE.md) | .agents/skills/ (native) |
| opencode | AGENTS.md (native) | — |
| Claude Code | CLAUDE.md (@AGENTS.md + Claude-only notes) | .claude/skills/ (native) |
Shims exist because Claude Code reads CLAUDE.md and not AGENTS.md, and
because it discovers skills under .claude/skills/ while Codex and Pi use
.agents/skills/:
CLAUDE.mdis a real file whose first line is@AGENTS.md. An import is used rather than a symlink because Windows symlinks need Administrator or Developer Mode, and this repo supports Windows contributors.- The replay skill has exactly one real copy, at
skills/replay/— the published path that.claude-plugin/plugin.jsonand the README install command point at..agents/skills/replayand.claude/skills/replayare both symlinks into it. On a Windows clone, rungit config core.symlinks trueand re-checkout before running the tests; the wiring guard intentionally rejects a copied directory.
test/agent-instructions.test.ts in packages/cli guards this wiring. It fails
if CLAUDE.md stops importing AGENTS.md, if either skill symlink breaks or
turns back into a copy, or if AGENTS.md outgrows the size limit below.
Size limit
Keep AGENTS.md under 32 KiB — Codex's default project_doc_max_bytes. Past
it Codex truncates the file and silently stops reading later sections. The guard
test enforces this. Check the actual file size before adding content; if space is
tight, split rather than append.
To split, move a package's gotchas into a nested pair inside that package:
packages/provider-cursor/AGENTS.md # the content
packages/provider-cursor/CLAUDE.md # one line: @AGENTS.mdBoth files are needed. Codex, Cursor, Pi, and opencode layer nested AGENTS.md;
Claude Code only layers nested CLAUDE.md, so it needs the sibling shim to see
the same text. Nested files load when an agent touches that subtree and cost
nothing elsewhere. Do not use .claude/rules/ for this — only Claude Code
reads it, which reintroduces exactly the split this layout removes.
Adding a skill
Create it at skills/<name>/SKILL.md, then link both discovery locations:
ln -s ../../skills/<name> .agents/skills/<name> # Codex, Pi
ln -s ../../skills/<name> .claude/skills/<name> # Claude CodeNever copy a skill into a discovery directory — the two locations drift silently and nothing catches it until an agent acts on a stale version.
See CONTRIBUTING.md for full architecture details.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

