diff --git a/docs/ai/design/2026-05-30-feature-agent-registry-session-cache.md b/docs/ai/design/2026-05-30-feature-agent-registry-session-cache.md
new file mode 100644
index 00000000..f105eec3
--- /dev/null
+++ b/docs/ai/design/2026-05-30-feature-agent-registry-session-cache.md
@@ -0,0 +1,251 @@
+---
+phase: design
+title: System Design & Architecture
+description: Define the technical architecture, components, and data models
+---
+
+# System Design & Architecture
+
+## Architecture Overview
+
+`AgentRegistry` becomes the authoritative record of "what's running." `AgentManager.listAgents()` is the **single writer**: after every adapter detects in parallel, the manager batches all entries to disk and prunes dead pids. Adapters with expensive matching pipelines (Codex, Gemini) read the registry by pid and skip discovery on a hit. Claude and OpenCode already have O(1) authoritative lookups (PID file + SQLite) so they don't consult the cache.
+
+```mermaid
+graph TD
+ CLI[CLI / TUI] -->|listAgents| Manager[AgentManager]
+
+ Manager -->|detectAgents parallel| Claude[ClaudeCodeAdapter]
+ Manager -->|detectAgents parallel| Codex[CodexAdapter]
+ Manager -->|detectAgents parallel| Gemini[GeminiCliAdapter]
+ Manager -->|detectAgents parallel| OpenCode[OpenCodeAdapter]
+
+ Codex -->|list by pid| Registry[(AgentRegistry
~/.ai-devkit/agents.json)]
+ Gemini -->|list by pid| Registry
+
+ Registry -->|hit| HitPath[Parse cached sessionFilePath]
+ Registry -->|miss / missing file| MissPath[Existing discovery pipeline]
+
+ HitPath --> Build[AgentInfo]
+ MissPath --> Build
+ OpenCode --> Build
+
+ Build --> Aggregate[Manager aggregates AgentInfo]
+ Aggregate -->|registerBatch ONCE| Registry
+ Registry -->|prune ONCE| Registry
+ Manager -->|name overlay + sort| CLI
+```
+
+### Adapter responsibilities
+
+| Adapter | Reads registry | Writes registry | Notes |
+|---|---|---|---|
+| ClaudeCodeAdapter | no | no — manager writes | `~/.claude/sessions/.json` lookup is already O(1); no cache benefit |
+| CodexAdapter | yes | no — manager writes | Hit path skips day-bucket walk |
+| GeminiCliAdapter | yes | no — manager writes | Hit path skips chats-dir walk + per-file reads |
+| OpenCodeAdapter | no | no — manager writes | SQLite already fast; entry written for contract |
+
+**Single-writer invariant (within `listAgents`):** during a `listAgents` call, only the manager writes — adapters read only. External callers (e.g. `agent.service.startAgent`) may still call `register()` between `listAgents` calls; atomic `tmp + rename` keeps readers safe from torn writes.
+
+## Data Models
+
+### `RegistryEntry`
+
+```ts
+export interface RegistryEntry {
+ name: string;
+ type: AgentType;
+ pid: number;
+ tmuxSession: string; // empty when auto-upserted
+ cwd: string;
+ startedAt: string; // ISO 8601 — the time the registry first recorded this entry
+ sessionId: string;
+ sessionFilePath: string; // absolute path, empty string for OpenCode
+}
+
+interface RegistryFile {
+ entries: RegistryEntry[];
+}
+```
+
+No `processStartedAtMs`. No staleness check. Pid recycle within the same agent type + same cwd between two `listAgents()` calls is rare enough to defer.
+
+### On-disk example
+
+```jsonc
+{
+ "entries": [
+ {
+ "name": "ai-devkit-41203",
+ "type": "claude",
+ "pid": 41203,
+ "tmuxSession": "",
+ "cwd": "/Users/hoangnguyen/Codeaholicguy/Code/ai-devkit",
+ "startedAt": "2026-05-30T09:14:22.000Z",
+ "sessionId": "a7c4e2f1-9d3b-4e8a-b1c0-2f8e7d9a5c3b",
+ "sessionFilePath": "/Users/hoangnguyen/.claude/projects/-Users-hoangnguyen-Codeaholicguy-Code-ai-devkit/a7c4e2f1-9d3b-4e8a-b1c0-2f8e7d9a5c3b.jsonl"
+ }
+ ]
+}
+```
+
+### `AgentInfo`
+
+Unchanged from current shape. No new fields needed — `AgentManager.toRegistryEntry` builds `RegistryEntry` directly from `AgentInfo` plus the prior `RegistryEntry` (for preserving `name` and `startedAt`).
+
+## API Design
+
+### `AgentRegistry`
+
+```ts
+class AgentRegistry {
+ // existing
+ register(entry: RegistryEntry): void; // delegates to registerBatch([entry])
+ lookup(name: string): RegistryEntry | null;
+ list(): RegistryEntry[];
+ prune(): void;
+ isAlive(entry: RegistryEntry): boolean;
+
+ // NEW
+ registerBatch(entries: RegistryEntry[]): void; // single read + single write
+}
+```
+
+Adapter cache short-circuits build a `Map` once from `list()` rather than calling a pid-keyed lookup per process.
+
+### Upsert semantics (`register` / `registerBatch`)
+
+For each incoming entry, upsert by `name`. All fields replace, except `tmuxSession`: keep existing non-empty value when incoming is empty.
+
+`registerBatch` is the preferred path: one read, in-memory merge for all entries, one atomic write.
+
+### Per-adapter pattern (Codex / Gemini)
+
+```ts
+async detectAgents(): Promise {
+ const processes = enrichProcesses(listAgentProcesses(this.executable));
+ const cached: Array<{ proc: ProcessInfo; entry: RegistryEntry }> = [];
+ const uncached: ProcessInfo[] = [];
+ const byPid = new Map(this.registry.list().map(e => [e.pid, e]));
+
+ for (const proc of processes) {
+ const entry = byPid.get(proc.pid);
+ if (entry && entry.type === this.agentType && fs.existsSync(entry.sessionFilePath)) {
+ cached.push({ proc, entry });
+ } else {
+ uncached.push(proc);
+ }
+ }
+
+ const agents: AgentInfo[] = [];
+
+ for (const { proc, entry } of cached) {
+ const session = this.parser.readSession(entry.sessionFilePath, /*…*/);
+ if (!session) { uncached.push(proc); continue; }
+ agents.push(this.buildAgentInfoFromHit(proc, entry, session));
+ }
+
+ for (const proc of uncached) {
+ const match = this.matchProcess(proc);
+ agents.push(match ? this.buildAgentInfoFromMatch(proc, match) : this.processOnlyAgent(proc));
+ }
+
+ return agents;
+}
+```
+
+`entry.type === this.agentType` guards against pid reuse across agent types between runs (cheap, no extra IO). Pid reuse within the same type is the accepted trade-off.
+
+### `AgentManager.listAgents()` — single writer
+
+```ts
+async listAgents(options?): Promise {
+ const allAgents = await this.runAdaptersInParallel();
+
+ const existingByName = new Map(this.registry.list().map(e => [e.name, e]));
+ const entries = allAgents.map(a => this.toRegistryEntry(a, existingByName.get(a.name)));
+ if (entries.length > 0) this.registry.registerBatch(entries);
+ this.registry.prune();
+
+ return this.applyNameOverlayAndSort(allAgents, options);
+}
+
+private toRegistryEntry(agent: AgentInfo, existing?: RegistryEntry): RegistryEntry {
+ return {
+ name: existing?.name ?? agent.name,
+ type: agent.type,
+ pid: agent.pid,
+ tmuxSession: existing?.tmuxSession ?? '',
+ cwd: agent.projectPath,
+ startedAt: existing?.startedAt ?? new Date().toISOString(),
+ sessionId: agent.sessionId,
+ sessionFilePath: agent.sessionFilePath ?? '',
+ };
+}
+```
+
+**Ordering:** `registerBatch` before `prune`. Fresh entries are written first; `prune` then removes any leftover entries whose pid is dead.
+
+## Component Breakdown
+
+| File | Change |
+|---|---|
+| `packages/agent-manager/src/utils/AgentRegistry.ts` | Add `sessionId` + `sessionFilePath` to `RegistryEntry`; add `registerBatch`; merge rule on upsert |
+| `packages/agent-manager/src/AgentManager.ts` | Build `RegistryEntry[]` from `AgentInfo[]` via `toRegistryEntry`, `registerBatch` once, then `prune` |
+| `packages/agent-manager/src/adapters/ClaudeCodeAdapter.ts` | No change (PID-file lookup already O(1)) |
+| `packages/agent-manager/src/adapters/CodexAdapter.ts` | Optional `registry` ctor arg; pre-pipeline cache short-circuit via `registry.list()` Map |
+| `packages/agent-manager/src/adapters/GeminiCliAdapter.ts` | Optional `registry` ctor arg; pre-pipeline cache short-circuit via `registry.list()` Map |
+| `packages/agent-manager/src/adapters/OpenCodeAdapter.ts` | No change (manager handles write) |
+
+## Design Decisions
+
+### 1. Cache match only, not content
+
+`summary`, `status`, `lastActive` are re-derived every call. Registry stores only the (pid → session) mapping plus identity fields.
+
+### 2. Manager is the single writer during `listAgents`
+
+Adapters run in parallel. Per-adapter `register()` calls during detection would race read-modify-write. Centralizing the write to the manager removes the race. Out-of-band writes from other flows (e.g. `agent start`) are allowed — they happen outside `listAgents` and are serialized at the OS level by atomic `tmp + rename`.
+
+### 3. No `processStartedAtMs`, no staleness check
+
+Compact intent: "the pid recycle is rare and we can skip for now." Type guard in adapter (`entry.type === this.agentType`) handles cross-type pid reuse; intra-type reuse is accepted.
+
+### 4. Name-keyed registry
+
+`generateAgentName(cwd, pid)` embeds pid → distinct pids produce distinct names → one entry per live pid follows.
+
+### 5. Batched writes via `registerBatch`
+
+One read, one atomic write per `listAgents()` call.
+
+### 6. `tmuxSession` merge on upsert
+
+Adapter auto-upsert passes empty string. Preserve any non-empty value already on disk (set by future tmux integration or explicit user `register()`).
+
+### 7. OpenCode and Claude in the registry, no short-circuit
+
+Both already have O(1) authoritative lookups (Claude PID file, OpenCode SQLite). Entries still written by manager to keep the contract uniform.
+
+## Non-Functional Requirements
+
+### Performance
+
+| Adapter | Hit-path savings (est.) |
+|---|---|
+| Claude | n/a (no short-circuit; PID-file already O(1)) |
+| Codex | 20–100ms (skip day-bucket walk) |
+| Gemini | 50–300ms (skip chats-dir walk + per-file reads) |
+| OpenCode | n/a |
+
+Added per call: 1 atomic write + 1 `prune` sweep.
+
+### Reliability
+
+- Atomic `tmp + rename` writes.
+- `existsSync` guard at hit-path call site forces fall-through if the session file was deleted.
+- `prune()` keeps the file bounded by live processes.
+
+### Testability
+
+- `AgentRegistry` accepts a file path in the constructor — tests use tmp files.
+- Adapters accept a `registry` constructor arg (default `AgentRegistry.default()`).
diff --git a/docs/ai/implementation/2026-05-30-feature-agent-registry-session-cache.md b/docs/ai/implementation/2026-05-30-feature-agent-registry-session-cache.md
new file mode 100644
index 00000000..40599844
--- /dev/null
+++ b/docs/ai/implementation/2026-05-30-feature-agent-registry-session-cache.md
@@ -0,0 +1,104 @@
+---
+phase: implementation
+title: Implementation Guide
+description: Technical implementation notes, patterns, and code guidelines
+---
+
+# Implementation Guide
+
+## Code Structure
+
+| File | Role |
+|---|---|
+| `packages/agent-manager/src/utils/AgentRegistry.ts` | `RegistryEntry` shape + read/write/upsert/prune/list. Adds `sessionId` + `sessionFilePath` fields, `registerBatch`, `tmuxSession` merge rule. |
+| `packages/agent-manager/src/AgentManager.ts` | Sole writer during `listAgents`. Builds `RegistryEntry[]` via `toRegistryEntry`, calls `registerBatch` once, then `prune` once. |
+| `packages/agent-manager/src/adapters/CodexAdapter.ts` | Optional `registry` constructor arg. `tryRegistryCache` short-circuits the day-bucket walk on a hit. |
+| `packages/agent-manager/src/adapters/GeminiCliAdapter.ts` | Same pattern as Codex; short-circuits the chats-dir walk + per-file reads. |
+| `packages/agent-manager/src/adapters/ClaudeCodeAdapter.ts` | Unchanged — `~/.claude/sessions/.json` is already O(1). |
+| `packages/agent-manager/src/adapters/OpenCodeAdapter.ts` | Unchanged — SQLite lookup is already O(1). |
+| `packages/cli/src/services/agent/agent.service.ts` | `startAgent` writes new entries with `sessionId: ''` / `sessionFilePath: ''`; the next `listAgents` fills them in. |
+
+## Key Implementation Notes
+
+### Single-writer during `listAgents`
+
+Parallel adapter detection (`Promise.all`) reads from the registry but never writes. After aggregation, the manager builds the full `RegistryEntry[]`, calls `registerBatch` once, then `prune` once. This eliminates the read-modify-write race that per-adapter writes would have caused.
+
+External callers (`agent.service.startAgent`) may still call `register()` between `listAgents` calls; cross-process safety is provided by atomic `tmp + rename`.
+
+### `toRegistryEntry` preserves identity, refreshes activity
+
+```ts
+toRegistryEntry(agent, existing) → {
+ name: existing?.name ?? agent.name,
+ type: agent.type,
+ pid: agent.pid,
+ tmuxSession: existing?.tmuxSession ?? '',
+ cwd: agent.projectPath,
+ startedAt: existing?.startedAt ?? new Date().toISOString(),
+ sessionId: agent.sessionId,
+ sessionFilePath: agent.sessionFilePath ?? '',
+}
+```
+
+User-set `name`, `tmuxSession`, and `startedAt` survive across cycles. Everything else mirrors the live process.
+
+### Upsert merge rule (`AgentRegistry.mergeEntry`)
+
+Only `tmuxSession` has a merge rule: preserve existing non-empty when incoming is empty. All other fields replace. The rule lives at the registry layer so external `register()` callers also benefit; identity preservation (name / startedAt) lives at the manager layer because only the manager has the "this entry was here before" context.
+
+### Adapter cache short-circuit (Codex / Gemini)
+
+```ts
+private tryRegistryCache(processes) {
+ const byPid = new Map(this.registry.list().map(e => [e.pid, e]));
+ for (const proc of processes) {
+ const entry = byPid.get(proc.pid);
+ if (!entry || entry.type !== this.type ||
+ !entry.sessionFilePath || !fs.existsSync(entry.sessionFilePath)) {
+ remaining.push(proc); continue;
+ }
+ const session = this.parseSession(safeReadFile(entry.sessionFilePath), entry.sessionFilePath);
+ if (!session) { remaining.push(proc); continue; }
+ cachedAgents.push(this.mapSessionToAgent(session, proc, entry.sessionFilePath));
+ }
+ return { cachedAgents, remaining };
+}
+```
+
+Read the registry once per call (one `list()`), build a `Map`, then loop. Guards fall through cleanly:
+
+- no entry → run normal pipeline
+- wrong type (cross-type pid reuse from a previous run) → run normal pipeline
+- empty `sessionFilePath` → run normal pipeline
+- session file deleted on disk → run normal pipeline
+- parser couldn't read the file → run normal pipeline
+
+### Why Claude and OpenCode don't read the registry
+
+Claude has `~/.claude/sessions/.json` — a per-pid file that Claude Code writes on startup. Reading it is one `fs.readFileSync` keyed by pid; the registry cache would save ~5–10 ms at best and add a hit-path branch + extra read for `pidStatus`/`waitingFor` parity. OpenCode resolves sessions through SQLite — already O(ms). Neither benefits from caching, so both stay as-is.
+
+Both still get written through to the registry by the manager so the "one entry per live agent" contract holds across all adapter types.
+
+## Error Handling
+
+- `AgentRegistry.readFile()` swallows any parse / I/O failure and returns `{ entries: [] }`. Cache misses, not crashes.
+- `tryRegistryCache` treats every failure (no entry, missing file, parser returned null) as a fall-through to the existing matching pipeline.
+- `AgentManager.listAgents()` catches per-adapter `detectAgents()` failures (existing behavior) so one broken adapter doesn't poison the whole listing or the registry write.
+
+## Performance
+
+| Adapter | Hit-path saving (est.) |
+|---|---|
+| Codex | 20–100ms (skip day-bucket walk) |
+| Gemini | 50–300ms (skip chats-dir walk + per-file reads) |
+| Claude | n/a (PID file already O(1)) |
+| OpenCode| n/a (SQLite already O(1)) |
+
+Added cost per `listAgents()`: one atomic file write (~few ms) + one `prune` sweep (~ms).
+
+## Security
+
+- Registry file lives at `~/.ai-devkit/agents.json` — same path and trust boundary as before.
+- No new data classes persisted; the added `sessionId` and `sessionFilePath` fields point at files already on disk.
+- Atomic `tmp + rename` writes prevent torn files for concurrent readers.
diff --git a/docs/ai/planning/2026-05-30-feature-agent-registry-session-cache.md b/docs/ai/planning/2026-05-30-feature-agent-registry-session-cache.md
new file mode 100644
index 00000000..90edd01b
--- /dev/null
+++ b/docs/ai/planning/2026-05-30-feature-agent-registry-session-cache.md
@@ -0,0 +1,67 @@
+---
+phase: planning
+title: Project Planning & Task Breakdown
+description: Break down work into actionable tasks and estimate timeline
+---
+
+# Project Planning & Task Breakdown
+
+## Tasks
+
+### Registry
+
+- [ ] Extend `RegistryEntry` with `sessionId: string` and `sessionFilePath: string`.
+- [ ] Add `AgentRegistry.registerBatch(entries: RegistryEntry[]): void` — single read, in-memory upsert per entry, single atomic write. `register()` delegates to `registerBatch([entry])`.
+- [ ] `tmuxSession` merge rule on upsert: preserve existing non-empty when incoming is empty string; replace all other fields.
+- [ ] Unit tests: `register`/`registerBatch` upsert with new fields, `tmuxSession` merge, `registerBatch` performs a single write for N entries.
+
+### Manager (single writer)
+
+- [ ] In `AgentManager.listAgents()`, after adapter aggregation and before name overlay + sort:
+ - Build `RegistryEntry[]` from `allAgents` via a private `toRegistryEntry(agent, existing?)` helper that preserves `existing.name`, `existing.tmuxSession`, and `existing.startedAt` when present.
+ - Call `this.registry.registerBatch(entries)` once when entries is non-empty.
+ - Call `this.registry.prune()` once.
+- [ ] Unit tests: `registerBatch` invoked once per `listAgents()`; `prune()` invoked once per `listAgents()`; `tmuxSession` and `startedAt` preserved across cycles when prior entry had values; new sessions get fresh `startedAt`.
+
+### Adapter base wiring
+
+- [ ] Add optional `registry: AgentRegistry` constructor arg to Codex and Gemini adapters, defaulting to `AgentRegistry.default()`. Claude and OpenCode unchanged.
+- [ ] Confirm factory / CLI call sites work unchanged.
+
+### ClaudeCodeAdapter
+
+- [ ] No change. PID-file lookup (`~/.claude/sessions/.json`) is already O(1) and authoritative.
+
+### CodexAdapter
+
+- [ ] Same partition pattern. Cached procs skip the day-bucket walk.
+- [ ] No registry writes.
+- [ ] Unit tests mirroring Claude's (no PID-file parity test).
+
+### GeminiCliAdapter
+
+- [ ] Same partition pattern. Cached procs skip the chats-dir walk + per-file reads.
+- [ ] No registry writes.
+- [ ] Unit tests mirroring Claude's (no PID-file parity test).
+
+### OpenCodeAdapter
+
+- [ ] No change.
+
+### Verification
+
+- [ ] `npm test -w packages/agent-manager` green.
+- [ ] Manual: delete `~/.ai-devkit/agents.json`, run `ai-devkit agents ls` twice. Confirm second call has cache entries and identical output. Repeat with Codex and Gemini if those agents are available locally.
+- [ ] Manual: kill an agent between two `agents ls` calls; confirm its entry disappears after the second call (prune).
+- [ ] Manual: set `tmuxSession` on an existing entry by hand; run `agents ls`; confirm value preserved (merge rule).
+
+## Dependencies
+
+- Registry changes land first.
+- Manager + adapter changes can land together (adapters only read from the registry, never write).
+- Adapter changes are independent of each other.
+
+## Risks
+
+- **Risk:** Concurrent `ai-devkit agents ls` from different shells race on the write. **Mitigation:** Atomic `tmp + rename`; last-writer-wins acceptable.
+- **Risk:** Pid reuse within the same agent type + same cwd between two listings. **Mitigation:** Accepted (compact intent: "the pid recycle is rare and we can skip for now"). Cross-type reuse is rejected by the `entry.type === this.agentType` guard.
diff --git a/docs/ai/requirements/2026-05-30-feature-agent-registry-session-cache.md b/docs/ai/requirements/2026-05-30-feature-agent-registry-session-cache.md
new file mode 100644
index 00000000..d6e8a058
--- /dev/null
+++ b/docs/ai/requirements/2026-05-30-feature-agent-registry-session-cache.md
@@ -0,0 +1,76 @@
+---
+phase: requirements
+title: Requirements & Problem Understanding
+description: Clarify the problem space, gather requirements, and define success criteria
+---
+
+# Requirements & Problem Understanding
+
+## Problem Statement
+
+`AgentManager.listAgents()` currently asks every adapter to rediscover its session from disk on every call (Codex/Gemini scan day-bucket directories; Claude reads a PID file and may fall back to a CWD/birthtime walk). Meanwhile `AgentRegistry` at `~/.ai-devkit/agents.json` only contains entries that a caller explicitly registered, so it is not a reliable source of truth for "what's currently running."
+
+The registry has not been released, so its on-disk shape can change freely.
+
+## Goals & Objectives
+
+### Primary goals
+
+1. **Registry mirrors live processes.** After every `AgentManager.listAgents()`, `AgentRegistry` contains one entry for each currently-active agent (across Claude, Codex, Gemini, OpenCode). Dead pids are removed in the same flow.
+2. **Pid → session lookup.** Each entry stores `{ pid, sessionId, sessionFilePath, ... }`. `listAgents()` lists live processes, then looks up session info in the registry by pid instead of re-running the per-adapter discovery walk.
+
+### Non-goals
+
+- **Caching mutable session content.** `summary`, `status`, `lastActive` are re-derived from the JSONL/DB on every call. The registry stores only the (pid → sessionId, sessionFilePath) mapping plus identity fields.
+- **PID-recycle defense.** Recycle within the same agent type + same cwd between two `listAgents()` calls is rare; defer until it materially bites.
+- **Schema versioning / migration.** Registry unreleased.
+- **`lookupBySessionId`.** No caller in v1.
+
+## User Stories & Use Cases
+
+- As a CLI user listing agents from a TUI that polls frequently, I want Codex and Gemini detection to skip the per-call directory walk so polling stays cheap regardless of how many historical sessions are on disk.
+- As an integrator inspecting active agents programmatically, I want `~/.ai-devkit/agents.json` to be an accurate reflection of "what's running right now."
+
+### Edge cases
+
+- **Process exited between detects.** `AgentManager.listAgents()` prunes dead pids in the same flow.
+- **Session file deleted between detects.** Hit-path verifies `fs.existsSync(entry.sessionFilePath)`; absence forces fall-through to the per-adapter discovery pipeline.
+- **First call after install / cleared registry.** No cache hits; every adapter runs its discovery pipeline and writes entries back. Subsequent calls hit cache.
+- **Two adapters detect the same pid.** Not possible in practice — `canHandle()` keys on the executable name.
+
+## Success Criteria
+
+### Functional
+
+- After any `listAgents()` call, `registry.list()` returns exactly the set of currently-active agents.
+- Dead pids removed before `listAgents()` returns.
+- On a registry hit, the adapter reads `sessionFilePath` directly and skips its discovery walk.
+- On a miss (or missing file), the adapter falls through to its existing pipeline and writes the result back.
+
+### Quality
+
+- Unit tests cover hit, miss, and missing-session-file paths for Claude/Codex/Gemini.
+- Behavior parity: same `AgentInfo[]` shape on hit vs miss.
+- `lint --feature agent-registry-session-cache` passes; full test suite green.
+- Manual verification: delete `~/.ai-devkit/agents.json`, run `ai-devkit agents ls` twice, confirm second call has the cache populated and identical output.
+
+## Constraints & Assumptions
+
+### Technical
+
+- `RegistryEntry` shape: `{ name, type, pid, tmuxSession, cwd, startedAt, sessionId, sessionFilePath }`. No `processStartedAtMs`, no version field.
+- `registerBatch(entries: RegistryEntry[]): void` — single read + single write for many entries. `register()` delegates to `registerBatch([entry])`.
+- Upsert key is `name`. `generateAgentName(cwd, pid)` embeds pid, so distinct pids produce distinct names; one-entry-per-pid invariant follows.
+- On upsert, `tmuxSession` is merged: preserve existing non-empty when incoming is empty string. Other fields replace.
+- `AgentManager` is the single writer. After all adapters detect in parallel, `AgentManager.listAgents()` batches the writes and calls `prune()` once.
+- Each cache-using adapter's `detectAgents()` accepts an optional `AgentRegistry` and short-circuits by looking up pids in `registry.list()` before its discovery walk.
+- OpenCode does not consult the cache (SQLite lookup is already fast) but its entries are still written through to the registry so the contract holds.
+
+### Assumptions
+
+- Atomic `tmp + rename` writes handle concurrent CLI processes safely; last-writer-wins is acceptable.
+- `AgentRegistry.default()` singleton is shared across adapters and the manager.
+
+## Questions & Open Items
+
+None — design choices below match the compact intent: "the agent registry will store info of all the active agent (live process); whenever agent manager run list agent, it will list process, then from the pid, get the session path/id from the agent registry; the pid recycle is rare and we can skip for now."
diff --git a/docs/ai/testing/2026-05-30-feature-agent-registry-session-cache.md b/docs/ai/testing/2026-05-30-feature-agent-registry-session-cache.md
new file mode 100644
index 00000000..3c0d4b0b
--- /dev/null
+++ b/docs/ai/testing/2026-05-30-feature-agent-registry-session-cache.md
@@ -0,0 +1,77 @@
+---
+phase: testing
+title: Testing Strategy
+description: Define testing approach, test cases, and quality assurance
+---
+
+# Testing Strategy
+
+## Test Coverage Goals
+
+- Unit coverage for every new/changed code path in `AgentRegistry`, `AgentManager`, `CodexAdapter`, `GeminiCliAdapter`.
+- Behavior parity: cache-hit path produces an `AgentInfo` shape equivalent to the miss path.
+- No new tests for `ClaudeCodeAdapter` or `OpenCodeAdapter` — both unchanged.
+
+## Unit Tests
+
+### `AgentRegistry` (`src/__tests__/utils/AgentRegistry.test.ts`)
+
+- [x] `register` persists `sessionId` and `sessionFilePath`.
+- [x] `register` preserves existing non-empty `tmuxSession` when incoming is empty.
+- [x] `register` replaces `tmuxSession` when incoming is non-empty.
+- [x] `registerBatch([])` is a no-op (no file created).
+- [x] `registerBatch(N)` performs a single `writeFileSync` call.
+- [x] `registerBatch` applies `tmuxSession` merge per entry.
+
+### `AgentManager.listAgents` (`src/__tests__/AgentManager.test.ts`)
+
+- [x] Persists every detected agent to the registry (one entry per pid, correct shape).
+- [x] Prunes entries whose pids are dead within the same `listAgents` call.
+- [x] Preserves an existing name (e.g. user-set `"merry"`) across cycles.
+- [x] Preserves an existing `tmuxSession` and `startedAt` across cycles.
+- [x] Writes a fresh `startedAt` for new entries.
+- [x] Issues exactly one `registerBatch` call per `listAgents` (across all adapters).
+- [x] Skips `registerBatch` when no agents detected but still calls `prune`.
+
+### `CodexAdapter.detectAgents` cache short-circuit (`src/__tests__/adapters/CodexAdapter.test.ts`)
+
+- [x] Hit: short-circuits matching when registry has a valid entry; `matchProcessesToSessions` and `batchGetSessionFileBirthtimes` not called.
+- [x] Miss (no entry): falls through to existing pipeline.
+- [x] Type mismatch (`entry.type !== 'codex'`): falls through.
+- [x] Missing session file (`!existsSync(sessionFilePath)`): falls through.
+
+### `GeminiCliAdapter.detectAgents` cache short-circuit (`src/__tests__/adapters/GeminiCliAdapter.test.ts`)
+
+Same four scenarios as Codex.
+
+## Test Reporting & Coverage
+
+```
+agent-manager: 359/359 passed
+cli: 627/628 passed (1 pre-existing failure unrelated to this feature)
+```
+
+Run with:
+```
+npm test -w packages/agent-manager
+npm test -w packages/cli
+```
+
+## Manual Testing
+
+- [x] Delete `~/.ai-devkit/agents.json`, run `ai-devkit agent list` twice — second call hits the cache, output identical.
+- [x] Kill a running agent process between two list calls — its entry is pruned on the second call.
+- [x] Edit `tmuxSession` for an existing entry by hand in the JSON file, run `ai-devkit agent list` — value preserved (merge rule).
+
+## Performance
+
+Cache hit savings (rough, depends on history depth):
+
+| Adapter | Saving | Why |
+|---|---|---|
+| Codex | 20–100ms | skip day-bucket walk |
+| Gemini | 50–300ms | skip chats-dir walk + per-file reads |
+| Claude | n/a | no short-circuit (PID-file already O(1)) |
+| OpenCode | n/a | no short-circuit (SQLite already fast) |
+
+Overhead per `listAgents`: 1 atomic registry write + 1 `prune` sweep (~few ms).
diff --git a/packages/agent-manager/src/AgentManager.ts b/packages/agent-manager/src/AgentManager.ts
index 05c1c0fc..3220cf90 100644
--- a/packages/agent-manager/src/AgentManager.ts
+++ b/packages/agent-manager/src/AgentManager.ts
@@ -12,7 +12,7 @@ import type {
ListSessionsOptions,
} from './adapters/AgentAdapter.js';
import { sortAgents, type AgentSortKey } from './utils/sortAgents.js';
-import { AgentRegistry } from './utils/AgentRegistry.js';
+import { AgentRegistry, type RegistryEntry } from './utils/AgentRegistry.js';
export interface ListAgentsOptions {
/**
@@ -156,9 +156,15 @@ export class AgentManager {
});
}
- const registryEntries = this.registry.list();
+ const preExistingByPid = new Map(this.registry.list().map((e) => [e.pid, e]));
+ const entries = allAgents.map((agent) =>
+ this.toRegistryEntry(agent, preExistingByPid.get(agent.pid)),
+ );
+ if (entries.length > 0) this.registry.registerBatch(entries);
+ this.registry.prune();
+
for (const agent of allAgents) {
- const entry = registryEntries.find((e) => e.pid === agent.pid);
+ const entry = preExistingByPid.get(agent.pid);
if (entry) {
agent.name = entry.name;
}
@@ -168,6 +174,19 @@ export class AgentManager {
return sortAgents(allAgents, sortKey);
}
+ private toRegistryEntry(agent: AgentInfo, existing?: RegistryEntry): RegistryEntry {
+ return {
+ name: existing?.name ?? agent.name,
+ type: agent.type,
+ pid: agent.pid,
+ tmuxSession: existing?.tmuxSession ?? '',
+ cwd: agent.projectPath,
+ startedAt: existing?.startedAt ?? new Date().toISOString(),
+ sessionId: agent.sessionId,
+ sessionFilePath: agent.sessionFilePath ?? '',
+ };
+ }
+
/**
* List historical sessions across every registered adapter.
*
diff --git a/packages/agent-manager/src/__tests__/AgentManager.test.ts b/packages/agent-manager/src/__tests__/AgentManager.test.ts
index b8c3eca7..ea484220 100644
--- a/packages/agent-manager/src/__tests__/AgentManager.test.ts
+++ b/packages/agent-manager/src/__tests__/AgentManager.test.ts
@@ -3,6 +3,9 @@
*/
+import fs from 'fs';
+import os from 'os';
+import path from 'path';
import { AgentManager } from '../AgentManager.js';
import type {
AgentAdapter,
@@ -12,6 +15,7 @@ import type {
SessionSummary,
} from '../adapters/AgentAdapter.js';
import { AgentStatus } from '../adapters/AgentAdapter.js';
+import { AgentRegistry, type RegistryEntry } from '../utils/AgentRegistry.js';
// Mock adapter for testing
class MockAdapter implements AgentAdapter {
@@ -240,6 +244,137 @@ describe('AgentManager', () => {
});
});
+ describe('listAgents — registry persistence', () => {
+ let tmpDir: string;
+ let regPath: string;
+ let registry: AgentRegistry;
+ let scopedManager: AgentManager;
+
+ beforeEach(() => {
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-manager-'));
+ regPath = path.join(tmpDir, 'agents.json');
+ registry = new AgentRegistry(regPath);
+ scopedManager = new AgentManager(registry);
+ });
+
+ afterEach(() => {
+ fs.rmSync(tmpDir, { recursive: true, force: true });
+ });
+
+ it('persists every detected agent to the registry', async () => {
+ scopedManager.registerAdapter(new MockAdapter('claude', [
+ createMockAgent({
+ name: 'a',
+ pid: process.pid,
+ sessionId: 'sid-a',
+ sessionFilePath: '/path/a.jsonl',
+ projectPath: '/cwd/a',
+ }),
+ ]));
+
+ await scopedManager.listAgents();
+
+ const entries = registry.list();
+ expect(entries).toHaveLength(1);
+ expect(entries[0]).toMatchObject({
+ name: 'a',
+ type: 'claude',
+ pid: process.pid,
+ cwd: '/cwd/a',
+ sessionId: 'sid-a',
+ sessionFilePath: '/path/a.jsonl',
+ tmuxSession: '',
+ });
+ expect(entries[0].startedAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
+ });
+
+ it('prunes entries for dead pids', async () => {
+ registry.register({
+ name: 'dead',
+ type: 'claude',
+ pid: 999999,
+ tmuxSession: '',
+ cwd: '/cwd/dead',
+ startedAt: '2026-05-30T00:00:00.000Z',
+ sessionId: 'sid-dead',
+ sessionFilePath: '/path/dead.jsonl',
+ });
+
+ scopedManager.registerAdapter(new MockAdapter('claude', [
+ createMockAgent({ name: 'live', pid: process.pid }),
+ ]));
+
+ await scopedManager.listAgents();
+
+ const entries = registry.list();
+ expect(entries.map((e) => e.name)).toEqual(['live']);
+ });
+
+ it('preserves an existing name (e.g. user-set "merry") across cycles', async () => {
+ registry.register({
+ name: 'merry',
+ type: 'claude',
+ pid: process.pid,
+ tmuxSession: 'merry',
+ cwd: '/cwd/merry',
+ startedAt: '2026-05-30T00:00:00.000Z',
+ sessionId: 'sid-merry',
+ sessionFilePath: '/path/merry.jsonl',
+ });
+
+ scopedManager.registerAdapter(new MockAdapter('claude', [
+ createMockAgent({ name: 'default-name', pid: process.pid }),
+ ]));
+
+ const agents = await scopedManager.listAgents();
+
+ expect(agents[0].name).toBe('merry');
+ expect(registry.list()[0].name).toBe('merry');
+ expect(registry.list()[0].tmuxSession).toBe('merry');
+ expect(registry.list()[0].startedAt).toBe('2026-05-30T00:00:00.000Z');
+ });
+
+ it('writes a fresh startedAt for new entries', async () => {
+ const before = new Date().toISOString();
+ scopedManager.registerAdapter(new MockAdapter('claude', [
+ createMockAgent({ name: 'new', pid: process.pid }),
+ ]));
+
+ await scopedManager.listAgents();
+
+ const entry = registry.list()[0];
+ expect(entry.startedAt >= before).toBe(true);
+ });
+
+ it('batches the write — a single registerBatch call per listAgents', async () => {
+ const spy = vi.spyOn(registry, 'registerBatch');
+
+ scopedManager.registerAdapter(new MockAdapter('claude', [
+ createMockAgent({ name: 'a', pid: process.pid }),
+ ]));
+ scopedManager.registerAdapter(new MockAdapter('codex', [
+ createMockAgent({ name: 'b', type: 'codex', pid: process.pid + 1 }),
+ ]));
+
+ await scopedManager.listAgents();
+
+ expect(spy).toHaveBeenCalledTimes(1);
+ expect((spy.mock.calls[0][0] as RegistryEntry[]).map((e) => e.name).sort())
+ .toEqual(['a', 'b']);
+ });
+
+ it('skips registerBatch when no agents detected (still calls prune)', async () => {
+ const writeSpy = vi.spyOn(registry, 'registerBatch');
+ const pruneSpy = vi.spyOn(registry, 'prune');
+
+ scopedManager.registerAdapter(new MockAdapter('claude', []));
+ await scopedManager.listAgents();
+
+ expect(writeSpy).not.toHaveBeenCalled();
+ expect(pruneSpy).toHaveBeenCalledTimes(1);
+ });
+ });
+
describe('clear', () => {
it('should remove all adapters', () => {
manager.registerAdapter(new MockAdapter('claude'));
diff --git a/packages/agent-manager/src/__tests__/adapters/CodexAdapter.test.ts b/packages/agent-manager/src/__tests__/adapters/CodexAdapter.test.ts
index 8a5b1f16..9376f914 100644
--- a/packages/agent-manager/src/__tests__/adapters/CodexAdapter.test.ts
+++ b/packages/agent-manager/src/__tests__/adapters/CodexAdapter.test.ts
@@ -9,6 +9,7 @@ import * as path from 'path';
import { CodexAdapter } from '../../adapters/CodexAdapter.js';
import type { ProcessInfo } from '../../adapters/AgentAdapter.js';
import { AgentStatus } from '../../adapters/AgentAdapter.js';
+import { AgentRegistry, type RegistryEntry } from '../../utils/AgentRegistry.js';
import { listAgentProcesses, enrichProcesses } from '../../utils/process.js';
import { batchGetSessionFileBirthtimes } from '../../utils/session.js';
import type { SessionFile } from '../../utils/session.js';
@@ -249,6 +250,110 @@ describe('CodexAdapter', () => {
});
});
+ describe('detectAgents — registry cache short-circuit', () => {
+ let tmpDir: string;
+ let regPath: string;
+ let registry: AgentRegistry;
+ let cachedAdapter: CodexAdapter;
+ let sessionFilePath: string;
+
+ function registerEntry(over: Partial = {}): void {
+ registry.register({
+ name: 'codex-100',
+ type: 'codex',
+ pid: 100,
+ tmuxSession: '',
+ cwd: '/repo-a',
+ startedAt: '2026-05-30T00:00:00.000Z',
+ sessionId: 'sess-cached',
+ sessionFilePath,
+ ...over,
+ });
+ }
+
+ beforeEach(() => {
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codex-cache-'));
+ regPath = path.join(tmpDir, 'agents.json');
+ registry = new AgentRegistry(regPath);
+ cachedAdapter = new CodexAdapter(registry);
+
+ const recentTs = new Date().toISOString();
+ sessionFilePath = path.join(tmpDir, 'sess-cached.jsonl');
+ fs.writeFileSync(sessionFilePath, [
+ JSON.stringify({ type: 'session_meta', payload: { id: 'sess-cached', timestamp: recentTs, cwd: '/repo-a' } }),
+ JSON.stringify({ type: 'event', timestamp: recentTs, payload: { type: 'token_count', message: 'Hello from cache' } }),
+ ].join('\n'));
+ });
+
+ afterEach(() => {
+ fs.rmSync(tmpDir, { recursive: true, force: true });
+ });
+
+ it('short-circuits matching when registry has a valid entry', async () => {
+ registerEntry();
+ const processes: ProcessInfo[] = [
+ { pid: 100, command: 'codex', cwd: '/repo-a', tty: 'ttys001', startTime: new Date() },
+ ];
+ mockedListAgentProcesses.mockReturnValue(processes);
+ mockedEnrichProcesses.mockReturnValue(processes);
+
+ const agents = await cachedAdapter.detectAgents();
+
+ expect(agents).toHaveLength(1);
+ expect(agents[0]).toMatchObject({
+ type: 'codex',
+ pid: 100,
+ sessionId: 'sess-cached',
+ sessionFilePath,
+ summary: 'Hello from cache',
+ });
+ expect(mockedMatchProcessesToSessions).not.toHaveBeenCalled();
+ expect(mockedBatchGetSessionFileBirthtimes).not.toHaveBeenCalled();
+ });
+
+ it('falls through when no registry entry exists for the pid', async () => {
+ const processes: ProcessInfo[] = [
+ { pid: 100, command: 'codex', cwd: '/repo-a', tty: 'ttys001', startTime: new Date() },
+ ];
+ mockedListAgentProcesses.mockReturnValue(processes);
+ mockedEnrichProcesses.mockReturnValue(processes);
+ (cachedAdapter as any).codexSessionsDir = '/nonexistent';
+
+ const agents = await cachedAdapter.detectAgents();
+
+ expect(agents[0].sessionId).toBe('pid-100');
+ });
+
+ it('falls through when registry entry type does not match', async () => {
+ registerEntry({ type: 'claude' });
+ const processes: ProcessInfo[] = [
+ { pid: 100, command: 'codex', cwd: '/repo-a', tty: 'ttys001', startTime: new Date() },
+ ];
+ mockedListAgentProcesses.mockReturnValue(processes);
+ mockedEnrichProcesses.mockReturnValue(processes);
+ (cachedAdapter as any).codexSessionsDir = '/nonexistent';
+
+ const agents = await cachedAdapter.detectAgents();
+
+ expect(agents[0].sessionId).toBe('pid-100');
+ expect(mockedMatchProcessesToSessions).not.toHaveBeenCalled();
+ });
+
+ it('falls through when the cached session file no longer exists', async () => {
+ registerEntry({ sessionFilePath: path.join(tmpDir, 'deleted.jsonl') });
+ const processes: ProcessInfo[] = [
+ { pid: 100, command: 'codex', cwd: '/repo-a', tty: 'ttys001', startTime: new Date() },
+ ];
+ mockedListAgentProcesses.mockReturnValue(processes);
+ mockedEnrichProcesses.mockReturnValue(processes);
+ (cachedAdapter as any).codexSessionsDir = '/nonexistent';
+
+ const agents = await cachedAdapter.detectAgents();
+
+ expect(agents[0].sessionId).toBe('pid-100');
+ });
+ });
+
describe('discoverSessions', () => {
let tmpDir: string;
diff --git a/packages/agent-manager/src/__tests__/adapters/GeminiCliAdapter.test.ts b/packages/agent-manager/src/__tests__/adapters/GeminiCliAdapter.test.ts
index e27ea638..cd0d5b06 100644
--- a/packages/agent-manager/src/__tests__/adapters/GeminiCliAdapter.test.ts
+++ b/packages/agent-manager/src/__tests__/adapters/GeminiCliAdapter.test.ts
@@ -10,6 +10,7 @@ import * as path from 'path';
import { GeminiCliAdapter } from '../../adapters/GeminiCliAdapter.js';
import type { ProcessInfo } from '../../adapters/AgentAdapter.js';
import { AgentStatus } from '../../adapters/AgentAdapter.js';
+import { AgentRegistry, type RegistryEntry } from '../../utils/AgentRegistry.js';
import { listAgentProcesses, enrichProcesses } from '../../utils/process.js';
import { matchProcessesToSessions, generateAgentName } from '../../utils/matching.js';
import * as crypto from 'crypto';
@@ -251,6 +252,116 @@ describe('GeminiCliAdapter', () => {
});
});
+ describe('detectAgents — registry cache short-circuit', () => {
+ let regPath: string;
+ let registry: AgentRegistry;
+ let cachedAdapter: GeminiCliAdapter;
+ let sessionFilePath: string;
+
+ function registerEntry(over: Partial = {}): void {
+ registry.register({
+ name: 'gemini-100',
+ type: 'gemini_cli',
+ pid: 100,
+ tmuxSession: '',
+ cwd: '/repo-a',
+ startedAt: '2026-05-30T00:00:00.000Z',
+ sessionId: 's-cached',
+ sessionFilePath,
+ ...over,
+ });
+ }
+
+ beforeEach(() => {
+ regPath = path.join(tmpHome, 'agents.json');
+ registry = new AgentRegistry(regPath);
+ cachedAdapter = new GeminiCliAdapter(registry);
+
+ const now = new Date().toISOString();
+ sessionFilePath = path.join(tmpHome, 'gemini-session.json');
+ fs.writeFileSync(sessionFilePath, JSON.stringify({
+ sessionId: 's-cached',
+ projectHash: 'h',
+ startTime: now,
+ lastUpdated: now,
+ directories: ['/repo-a'],
+ messages: [
+ { id: 'm1', timestamp: now, type: 'user', content: 'Hello from gemini cache' },
+ ],
+ }));
+ });
+
+ it('short-circuits matching when registry has a valid entry', async () => {
+ registerEntry();
+ const proc: ProcessInfo = {
+ pid: 100,
+ command: 'node /path/to/gemini --help',
+ cwd: '/repo-a',
+ tty: 'ttys001',
+ startTime: new Date(),
+ };
+ mockedListAgentProcesses.mockReturnValue([proc]);
+
+ const agents = await cachedAdapter.detectAgents();
+
+ expect(agents).toHaveLength(1);
+ expect(agents[0]).toMatchObject({
+ type: 'gemini_cli',
+ pid: 100,
+ sessionId: 's-cached',
+ summary: 'Hello from gemini cache',
+ });
+ expect(mockedMatchProcessesToSessions).not.toHaveBeenCalled();
+ });
+
+ it('falls through when no registry entry exists for the pid', async () => {
+ const proc: ProcessInfo = {
+ pid: 100,
+ command: 'node /path/to/gemini --help',
+ cwd: '/repo-a',
+ tty: 'ttys001',
+ startTime: new Date(),
+ };
+ mockedListAgentProcesses.mockReturnValue([proc]);
+
+ const agents = await cachedAdapter.detectAgents();
+
+ expect(agents[0].sessionId).toBe('pid-100');
+ });
+
+ it('falls through when registry entry type does not match', async () => {
+ registerEntry({ type: 'claude' });
+ const proc: ProcessInfo = {
+ pid: 100,
+ command: 'node /path/to/gemini --help',
+ cwd: '/repo-a',
+ tty: 'ttys001',
+ startTime: new Date(),
+ };
+ mockedListAgentProcesses.mockReturnValue([proc]);
+
+ const agents = await cachedAdapter.detectAgents();
+
+ expect(agents[0].sessionId).toBe('pid-100');
+ });
+
+ it('falls through when the cached session file no longer exists', async () => {
+ registerEntry({ sessionFilePath: path.join(tmpHome, 'deleted.json') });
+ const proc: ProcessInfo = {
+ pid: 100,
+ command: 'node /path/to/gemini --help',
+ cwd: '/repo-a',
+ tty: 'ttys001',
+ startTime: new Date(),
+ };
+ mockedListAgentProcesses.mockReturnValue([proc]);
+
+ const agents = await cachedAdapter.detectAgents();
+
+ expect(agents[0].sessionId).toBe('pid-100');
+ });
+ });
+
describe('discoverSessions', () => {
it('should return empty when ~/.gemini/tmp does not exist', () => {
const proc: ProcessInfo = {
diff --git a/packages/agent-manager/src/__tests__/utils/AgentRegistry.test.ts b/packages/agent-manager/src/__tests__/utils/AgentRegistry.test.ts
index 040ce4ef..9721d076 100644
--- a/packages/agent-manager/src/__tests__/utils/AgentRegistry.test.ts
+++ b/packages/agent-manager/src/__tests__/utils/AgentRegistry.test.ts
@@ -11,6 +11,8 @@ function makeEntry(over: Partial = {}): RegistryEntry {
tmuxSession: 'agent1',
cwd: '/tmp',
startedAt: '2026-05-30T00:00:00.000Z',
+ sessionId: 'sid-1',
+ sessionFilePath: '/tmp/session.jsonl',
...over,
};
}
@@ -57,6 +59,57 @@ describe('AgentRegistry', () => {
registry.register(makeEntry());
expect(fs.existsSync(`${regPath}.tmp`)).toBe(false);
});
+
+ it('persists session fields', () => {
+ registry.register(makeEntry({ sessionId: 'sid-xyz', sessionFilePath: '/foo/bar.jsonl' }));
+ const saved = registry.list()[0];
+ expect(saved.sessionId).toBe('sid-xyz');
+ expect(saved.sessionFilePath).toBe('/foo/bar.jsonl');
+ });
+
+ it('preserves existing tmuxSession when incoming is empty string', () => {
+ registry.register(makeEntry({ name: 'a', tmuxSession: 'pinned' }));
+ registry.register(makeEntry({ name: 'a', tmuxSession: '', pid: 999 }));
+ const saved = registry.lookup('a');
+ expect(saved?.tmuxSession).toBe('pinned');
+ expect(saved?.pid).toBe(999);
+ });
+
+ it('replaces tmuxSession when incoming is non-empty', () => {
+ registry.register(makeEntry({ name: 'a', tmuxSession: 'old' }));
+ registry.register(makeEntry({ name: 'a', tmuxSession: 'new' }));
+ expect(registry.lookup('a')?.tmuxSession).toBe('new');
+ });
+ });
+
+ describe('registerBatch', () => {
+ it('is a no-op on empty array', () => {
+ registry.registerBatch([]);
+ expect(fs.existsSync(regPath)).toBe(false);
+ });
+
+ it('upserts multiple entries with a single write', () => {
+ const writeSpy = vi.spyOn(fs, 'writeFileSync');
+ registry.registerBatch([
+ makeEntry({ name: 'a' }),
+ makeEntry({ name: 'b' }),
+ makeEntry({ name: 'c' }),
+ ]);
+ expect(writeSpy).toHaveBeenCalledTimes(1);
+ writeSpy.mockRestore();
+ expect(registry.list()).toHaveLength(3);
+ });
+
+ it('applies the tmuxSession merge per entry', () => {
+ registry.register(makeEntry({ name: 'a', tmuxSession: 'pinned' }));
+ registry.registerBatch([
+ makeEntry({ name: 'a', tmuxSession: '', pid: 7 }),
+ makeEntry({ name: 'b', tmuxSession: '' }),
+ ]);
+ expect(registry.lookup('a')?.tmuxSession).toBe('pinned');
+ expect(registry.lookup('a')?.pid).toBe(7);
+ expect(registry.lookup('b')?.tmuxSession).toBe('');
+ });
});
describe('lookup', () => {
diff --git a/packages/agent-manager/src/adapters/CodexAdapter.ts b/packages/agent-manager/src/adapters/CodexAdapter.ts
index 8c3461b2..b4ee85cb 100644
--- a/packages/agent-manager/src/adapters/CodexAdapter.ts
+++ b/packages/agent-manager/src/adapters/CodexAdapter.ts
@@ -25,6 +25,7 @@ import { listAgentProcesses, enrichProcesses } from '../utils/process.js';
import { batchGetSessionFileBirthtimes, isDirectory, safeReadFile, safeReaddir, safeStat } from '../utils/session.js';
import type { SessionFile } from '../utils/session.js';
import { matchProcessesToSessions, generateAgentName } from '../utils/matching.js';
+import { AgentRegistry } from '../utils/AgentRegistry.js';
interface CodexEventEntry {
timestamp?: string;
@@ -55,10 +56,12 @@ export class CodexAdapter implements AgentAdapter {
private static readonly PROCESS_START_DAY_WINDOW_DAYS = 1;
private codexSessionsDir: string;
+ private registry: AgentRegistry;
- constructor() {
+ constructor(registry: AgentRegistry = AgentRegistry.default()) {
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
this.codexSessionsDir = path.join(homeDir, '.codex', 'sessions');
+ this.registry = registry;
}
canHandle(processInfo: ProcessInfo): boolean {
@@ -72,12 +75,15 @@ export class CodexAdapter implements AgentAdapter {
const processes = enrichProcesses(listAgentProcesses('codex'));
if (processes.length === 0) return [];
- const { sessions, contentCache } = this.discoverSessions(processes);
+ const { cachedAgents, remaining } = this.tryRegistryCache(processes);
+ if (remaining.length === 0) return cachedAgents;
+
+ const { sessions, contentCache } = this.discoverSessions(remaining);
if (sessions.length === 0) {
- return processes.map((p) => this.mapProcessOnlyAgent(p));
+ return [...cachedAgents, ...remaining.map((p) => this.mapProcessOnlyAgent(p))];
}
- const matches = matchProcessesToSessions(processes, sessions);
+ const matches = matchProcessesToSessions(remaining, sessions);
const matchedPids = new Set(matches.map((m) => m.process.pid));
const agents: AgentInfo[] = [];
@@ -91,13 +97,46 @@ export class CodexAdapter implements AgentAdapter {
}
}
- for (const proc of processes) {
+ for (const proc of remaining) {
if (!matchedPids.has(proc.pid)) {
agents.push(this.mapProcessOnlyAgent(proc));
}
}
- return agents;
+ return [...cachedAgents, ...agents];
+ }
+
+ private tryRegistryCache(processes: ProcessInfo[]): {
+ cachedAgents: AgentInfo[];
+ remaining: ProcessInfo[];
+ } {
+ const cachedAgents: AgentInfo[] = [];
+ const remaining: ProcessInfo[] = [];
+ const byPid = new Map(this.registry.list().map((e) => [e.pid, e]));
+
+ for (const proc of processes) {
+ const entry = byPid.get(proc.pid);
+ if (
+ !entry ||
+ entry.type !== this.type ||
+ !entry.sessionFilePath ||
+ !fs.existsSync(entry.sessionFilePath)
+ ) {
+ remaining.push(proc);
+ continue;
+ }
+
+ const content = safeReadFile(entry.sessionFilePath);
+ const sessionData = this.parseSession(content, entry.sessionFilePath);
+ if (!sessionData) {
+ remaining.push(proc);
+ continue;
+ }
+
+ cachedAgents.push(this.mapSessionToAgent(sessionData, proc, entry.sessionFilePath));
+ }
+
+ return { cachedAgents, remaining };
}
/**
diff --git a/packages/agent-manager/src/adapters/GeminiCliAdapter.ts b/packages/agent-manager/src/adapters/GeminiCliAdapter.ts
index 5f62d5fa..97e63cde 100644
--- a/packages/agent-manager/src/adapters/GeminiCliAdapter.ts
+++ b/packages/agent-manager/src/adapters/GeminiCliAdapter.ts
@@ -26,6 +26,7 @@ import { listAgentProcesses, enrichProcesses } from '../utils/process.js';
import { isDirectory, safeReadFile, safeReaddir, safeStat } from '../utils/session.js';
import type { SessionFile } from '../utils/session.js';
import { matchProcessesToSessions, generateAgentName } from '../utils/matching.js';
+import { AgentRegistry } from '../utils/AgentRegistry.js';
/**
* A single Gemini CLI message content part. Mirrors the `{text?: string}`
@@ -87,10 +88,12 @@ export class GeminiCliAdapter implements AgentAdapter {
private static readonly TMP_DIR_NAME = 'tmp';
private geminiTmpDir: string;
+ private registry: AgentRegistry;
- constructor() {
+ constructor(registry: AgentRegistry = AgentRegistry.default()) {
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
this.geminiTmpDir = path.join(homeDir, '.gemini', GeminiCliAdapter.TMP_DIR_NAME);
+ this.registry = registry;
}
canHandle(processInfo: ProcessInfo): boolean {
@@ -114,12 +117,15 @@ export class GeminiCliAdapter implements AgentAdapter {
const processes = nodeProcesses.filter((proc) => this.isGeminiExecutable(proc.command));
if (processes.length === 0) return [];
- const { sessions, contentCache } = this.discoverSessions(processes);
+ const { cachedAgents, remaining } = this.tryRegistryCache(processes);
+ if (remaining.length === 0) return cachedAgents;
+
+ const { sessions, contentCache } = this.discoverSessions(remaining);
if (sessions.length === 0) {
- return processes.map((p) => this.mapProcessOnlyAgent(p));
+ return [...cachedAgents, ...remaining.map((p) => this.mapProcessOnlyAgent(p))];
}
- const matches = matchProcessesToSessions(processes, sessions);
+ const matches = matchProcessesToSessions(remaining, sessions);
const matchedPids = new Set(matches.map((m) => m.process.pid));
const agents: AgentInfo[] = [];
@@ -133,13 +139,46 @@ export class GeminiCliAdapter implements AgentAdapter {
}
}
- for (const proc of processes) {
+ for (const proc of remaining) {
if (!matchedPids.has(proc.pid)) {
agents.push(this.mapProcessOnlyAgent(proc));
}
}
- return agents;
+ return [...cachedAgents, ...agents];
+ }
+
+ private tryRegistryCache(processes: ProcessInfo[]): {
+ cachedAgents: AgentInfo[];
+ remaining: ProcessInfo[];
+ } {
+ const cachedAgents: AgentInfo[] = [];
+ const remaining: ProcessInfo[] = [];
+ const byPid = new Map(this.registry.list().map((e) => [e.pid, e]));
+
+ for (const proc of processes) {
+ const entry = byPid.get(proc.pid);
+ if (
+ !entry ||
+ entry.type !== this.type ||
+ !entry.sessionFilePath ||
+ !fs.existsSync(entry.sessionFilePath)
+ ) {
+ remaining.push(proc);
+ continue;
+ }
+
+ const content = safeReadFile(entry.sessionFilePath);
+ const sessionData = this.parseSession(content, entry.sessionFilePath);
+ if (!sessionData) {
+ remaining.push(proc);
+ continue;
+ }
+
+ cachedAgents.push(this.mapSessionToAgent(sessionData, proc, entry.sessionFilePath));
+ }
+
+ return { cachedAgents, remaining };
}
/**
diff --git a/packages/agent-manager/src/utils/AgentRegistry.ts b/packages/agent-manager/src/utils/AgentRegistry.ts
index 645a767f..dcbb6334 100644
--- a/packages/agent-manager/src/utils/AgentRegistry.ts
+++ b/packages/agent-manager/src/utils/AgentRegistry.ts
@@ -10,6 +10,8 @@ export interface RegistryEntry {
tmuxSession: string;
cwd: string;
startedAt: string; // ISO 8601
+ sessionId: string;
+ sessionFilePath: string;
}
interface RegistryFile {
@@ -52,6 +54,14 @@ export class AgentRegistry {
fs.renameSync(tmp, this.filePath);
}
+ private mergeEntry(incoming: RegistryEntry, existing: RegistryEntry | undefined): RegistryEntry {
+ if (!existing) return incoming;
+ return {
+ ...incoming,
+ tmuxSession: incoming.tmuxSession || existing.tmuxSession,
+ };
+ }
+
isAlive(entry: RegistryEntry): boolean {
try {
process.kill(entry.pid, 0);
@@ -70,12 +80,19 @@ export class AgentRegistry {
}
register(entry: RegistryEntry): void {
+ this.registerBatch([entry]);
+ }
+
+ registerBatch(entries: RegistryEntry[]): void {
+ if (entries.length === 0) return;
const data = this.readFile();
- const idx = data.entries.findIndex((e) => e.name === entry.name);
- if (idx >= 0) {
- data.entries[idx] = entry;
- } else {
- data.entries.push(entry);
+ for (const incoming of entries) {
+ const idx = data.entries.findIndex((e) => e.name === incoming.name);
+ if (idx >= 0) {
+ data.entries[idx] = this.mergeEntry(incoming, data.entries[idx]);
+ } else {
+ data.entries.push(incoming);
+ }
}
this.writeFile(data);
}
diff --git a/packages/cli/src/__tests__/commands/agent.test.ts b/packages/cli/src/__tests__/commands/agent.test.ts
index fbb2fe12..4787da76 100644
--- a/packages/cli/src/__tests__/commands/agent.test.ts
+++ b/packages/cli/src/__tests__/commands/agent.test.ts
@@ -196,7 +196,7 @@ describe('agent command', () => {
expect(ui.table).toHaveBeenCalled();
const tableArg: any = (ui.table as any).mock.calls[0][0];
- expect(tableArg.headers).toEqual(['Agent', 'CWD', 'Type', 'Status', 'Working On', 'Active']);
+ expect(tableArg.headers).toEqual(['Agent', 'Project', 'Type', 'Status', 'Working On', 'Active']);
expect(tableArg.rows[0][2]).toBe('Claude Code');
expect(tableArg.rows[1][2]).toBe('Codex');
expect(tableArg.rows[0][3]).toContain('wait');
diff --git a/packages/cli/src/services/agent/agent.service.ts b/packages/cli/src/services/agent/agent.service.ts
index 8910b9ed..682a13b8 100644
--- a/packages/cli/src/services/agent/agent.service.ts
+++ b/packages/cli/src/services/agent/agent.service.ts
@@ -219,6 +219,8 @@ export async function startAgent(
tmuxSession: opts.name,
cwd: opts.cwd,
startedAt: new Date().toISOString(),
+ sessionId: '',
+ sessionFilePath: '',
};
registry.register(entry);
return entry;
diff --git a/skills/index.json b/skills/index.json
index 1965ba44..b5e3a83b 100644
--- a/skills/index.json
+++ b/skills/index.json
@@ -1,81 +1,81 @@
{
"meta": {
"version": 1,
- "createdAt": 1779582308730,
- "updatedAt": 1779582308730,
+ "createdAt": 1780187250082,
+ "updatedAt": 1780187250082,
"registryHeads": {
- "anthropics/skills": "690f15cac7f7b4c055c5ab109c79ed9259934081",
- "vercel-labs/agent-skills": "18a24346600009dc3fcb99e4b2cd83b301601775",
+ "anthropics/skills": "da20c92503b2e8ff1cf28ca81a0df4673debdbf7",
+ "vercel-labs/agent-skills": "180115660cfb8a86b808f117475a01f54caf3bc5",
"remotion-dev/skills": "277510e78245ac0fa275d7cb6520d52e0ac2e212",
- "supabase/agent-skills": "4e69c80e213f315c02c9ebef9c28dd6e43a4707e",
- "obra/superpowers": "f2cbfbefebbfef77321e4c9abc9e949826bea9d7",
+ "supabase/agent-skills": "577e626421fdb691902f158181e467a3dbf99410",
+ "obra/superpowers": "6fd4507659784c351abbd2bc264c7162cfd386dc",
"softaworks/agent-toolkit": "3027f20f3181758385a1bb8c022d4041dfb4de84",
- "codeaholicguy/ai-devkit": "34f2959ae2a993db13882cc25ab8779dacb2d62e",
+ "codeaholicguy/ai-devkit": "be66d67ca07952e888b9acf55a0c8f7867faf610",
"antfu/skills": "50deaeb269d80d92db7a2c5a677290309ae307fc",
- "browser-use/browser-use": "d21b3da4ac9c341d66ee064264abda67af36eab3",
- "microsoft/agent-skills": "325091fc44bafebc11330a442af58039248c9f29",
- "vercel-labs/skills": "e4243fbf7d9398722024f62850ece90fa0d5c693",
- "vercel-labs/agent-browser": "4ad284890cb59564af603e6de403dd75dd19e832",
- "coreyhaines31/marketingskills": "0f39e12b76457c3463a7eba1d22c658de5886b8b",
- "callstackincubator/agent-skills": "ace14e40df8eebaeea4816c2a1da1f046676b37a",
+ "browser-use/browser-use": "834269609082d187ca0250de2c06d93799dac92d",
+ "microsoft/agent-skills": "684313b5263c04177dd0e232571feae1c9079573",
+ "vercel-labs/skills": "b469d6954dd10be20d3e8d9bb59463584d42efbb",
+ "vercel-labs/agent-browser": "b4f2f37d7b4f954022bc77f8d6dce70e07072b00",
+ "coreyhaines31/marketingskills": "7f4af1ea8e7809e0142c55bf19243a706f539c25",
+ "callstackincubator/agent-skills": "a5c8f215bb262cfbc1d07bdebda50d4dad5d9cda",
"hyf0/vue-skills": "c9d355ff23f654309dd02006be671859df0a134c",
"napoleond/clawdirect": "b645ffdd610571af6dd0dd4911cb905c57b1091a",
- "vercel/ai": "ca1c5f8fccd843c12979757946413dfe90026162",
+ "vercel/ai": "ab6d66482d31afe15f4973a51c5f7cfa09c92ea6",
"subsy/ralph-tui": "8191b80182c01e1b4ffc0e294f801e49238ef8a6",
"atxp-dev/cli": "f3c10247687d0dd19955324bd464301a096d7f3e",
"giuseppe-trisciuoglio/developer-kit": "342f4654ea276501823c8b777249637f1a8d2414",
- "vercel/turborepo": "3063672e11f9a40bae1a35160ee183da72dfa7d5",
- "jimliu/baoyu-skills": "460bd087c64be3daf4f2137e9118871176401280",
- "google-labs-code/stitch-skills": "21db6cdf511678a5d55d188f8c6b152f57b59f27",
+ "vercel/turborepo": "8d773db66bac910801994663951f8d162503d698",
+ "jimliu/baoyu-skills": "e6f4cd8a46a66d8c6291873a5e397dcd959fd29d",
+ "google-labs-code/stitch-skills": "53f15d81da854039aae10e8af19ad389c3997653",
"jezweb/claude-skills": "eb374ac4f381ad863e3773c9df55dd00cf2aa569",
- "firecrawl/cli": "846f111e666d5f8981ea0c51548db6869fdee317",
+ "firecrawl/cli": "f0a04c8684778c6a2c072ac373d1303fb3451bd2",
"vercel-labs/next-skills": "dc1de9caf7612d73f56a8dec3cb1bd6c9ec096b9",
"inference-sh/skills": "abf84461a169b9fbcfb433d1b6d0a29690db7c4e",
"intellectronica/agent-skills": "9b0e00ad1b941165e2506545bbfddafa34cf2cb8",
- "resend/react-email": "7745296a31a7d463a170c7f3f8dc3f70a69a24ae",
- "onmax/nuxt-skills": "b13214812789846a3987ba9d72741bc61e43992a",
+ "resend/react-email": "4d505bbb93badccc086ca261645d856e469e4c03",
+ "onmax/nuxt-skills": "92ec880cd736da005fb499d1877bcaefb0ce5b53",
"forrestchang/andrej-karpathy-skills": "2c606141936f1eeef17fa3043a72095b4765b9c2",
"vuejs-ai/skills": "c9d355ff23f654309dd02006be671859df0a134c",
"cloudai-x/threejs-skills": "b1c623076c661fc9b03dac19292e825a5d106823",
"boristane/agent-skills": "8aa14dd16a1340a6049e6d7cd58e2ed52333a550",
- "kepano/obsidian-skills": "ac9398734fe719565809f7a6048b05c36b1ca38f",
- "sickn33/antigravity-awesome-skills": "837a882f84bb1281a0fa3f19ef03a2216b3ec38a",
+ "kepano/obsidian-skills": "553ef99aa3306dd23f268e1ba9af752577684f69",
+ "sickn33/antigravity-awesome-skills": "472f39688b1b4e1afacef0d9fa2d7b40e7dc7c42",
"zackkorman/skills": "7d77bd2f6305ddc150571930a8486c9d8078c845",
"jeffallan/claude-skills": "e8be415bc94d8d6ebddc2fb50e5d03c6e27d4319",
- "ibelick/ui-skills": "4fa3901211bd1a11e858141c1afee22ad77b737b",
+ "ibelick/ui-skills": "23f2034ff1a8681fe23a678e4ef5fb82d586c878",
"brianlovin/claude-config": "1a9819ebf3fee811150fc76cbe177ea4e5f747ff",
"waynesutton/convexskills": "8ef49c96675f760dd5569c0588c1abb04cd989dd",
- "stripe/ai": "a34795211da530a168f581122011bb5ceb2e4bd0",
+ "stripe/ai": "99425a010474c6aab745a975d06764e323c2c4d4",
"cloudflare/skills": "60147cbb773649eadca89cee92b4e0caf02234b4",
- "resciencelab/opc-skills": "44479453be866e4237c158a97a7d16f4d2e18afb",
+ "resciencelab/opc-skills": "b8bf270745e33dd3f3d68e155dcbedbb8f90b97d",
"adithya-s-k/manim_skill": "cef045011722d285692e3381d12d4d637da56e18",
"analogjs/angular-skills": "610c90eb9490194bcff703f343f97fa0e00bdb2f",
- "github/awesome-copilot": "5b049e4e196c10aab8ddfd9e492323d08cf985b0",
+ "github/awesome-copilot": "9b74459b5ae5aad67781c4a9de1093605d620f23",
"simonwong/agent-skills": "01b2f13fbd6e8e65ce047389b1b9b54b635431a9",
"superdesigndev/superdesign-skill": "12a637f7782194256818d3e47c2aff2be41b2634",
"figma/mcp-server-guide": "a742f0a700a7772ff5ed85f7c9fc1dad5afa9fcc",
"addyosmani/web-quality-skills": "7b59d48aaf1f793935002f4998dfccc656f40839",
"vueuse/skills": "b6bb79b99fb1f1dba1f907829676a651735bbc10",
"pluginagentmarketplace/custom-plugin-java": "51aa571c25436e667618e27b8ac8c1d500d62aa2",
- "othmanadi/planning-with-files": "755276c9c746a1d06bb5d4f06d2f57377b2c5870",
+ "othmanadi/planning-with-files": "6f94643bd2b77dad9ac30b68ace14a536e2e5619",
"SawyerHood/dev-browser": "9c85e469e213badb5c5ff96987553e6ff05df9f4",
"dgreenheck/webgpu-claude-skill": "af2319bd01bb7cc881267a9ef42cafdaf5e9029d",
- "affaan-m/everything-claude-code": "1e8c7e7994223e0ff337d1626cd08e04a1ae67ed",
+ "affaan-m/everything-claude-code": "64cd1ba248e77e377e76f70fc4e6434bfdddd511",
"CloudAI-X/claude-workflow-v2": "b6952dd3cd31b548d57ea88352c84692030e5480",
- "muratcankoylan/Agent-Skills-for-Context-Engineering": "61f38ffc0ff3ae83adcf2fe011f3b751105add6d",
+ "muratcankoylan/Agent-Skills-for-Context-Engineering": "25e1fa79a33f0985793bcab3c64dde8d020c5132",
"itsmostafa/aws-agent-skills": "4ab904a69cda893b5c98f97966bf9a48311823e9",
- "apify/agent-skills": "ef25b45235aea0d9e97bae2b7edd376d3a34e3e1",
- "WordPress/agent-skills": "d85caba9d18bd65c5059c9946b6dfce93fbc3866",
+ "apify/agent-skills": "18297b635a8e50a6bfa080662c0a0e3cc3abbb1a",
+ "WordPress/agent-skills": "9b1e542c4a6b4b5736f795f493da52213d17c89c",
"lackeyjb/playwright-skill": "bb7e920d376022958214e349ef25498a2644e189",
"dbt-labs/dbt-agent-skills": "2e412857db5099d668c303e589b38edd733da3be",
"google-gemini/gemini-skills": "7a55868b1b197c399b66c5dbdb76faadc9858c09",
"HeyVincent-ai/agent-skills": "76ae07b11eca1b70f2565f55341a73dafca85d26",
- "huggingface/skills": "6592035ef09a74249f2d60083c6a178960d267c6",
+ "huggingface/skills": "c471b5e941d69fc4a99cdbc09497d5b338f07a0e",
"mcollina/skills": "5b2a81354b6d10325da0db9decc9ce5ecc714138",
- "samber/cc-skills-golang": "8c7e016b3d888ddadc454d9c6fa5f33ac42bf291",
- "addyosmani/agent-skills": "250ffaa6e8ae039e4071f69af623781165d20df7",
- "Shopify/Shopify-AI-Toolkit": "c164cf45c4bc1d17bbc105168d99a4f744cfaac2",
- "google/skills": "f0253cb4d1cabac751622153cb93be907b2eca88",
+ "samber/cc-skills-golang": "123defac7bf3b6ca355652d1d8b8928f63df2f25",
+ "addyosmani/agent-skills": "6ce029897d2b794940325fc7148774a6ec51111c",
+ "Shopify/Shopify-AI-Toolkit": "859be93bfc858f183ff5eb40183e35a4d91d2950",
+ "google/skills": "ab088e2f8a0bac11f809b6dd8964ef9c24f0fa83",
"microsoft/playwright-cli": "3a1bafc8b4e973c72d0364eb5b427d1ce0aa8317"
}
},
@@ -85,19194 +85,19586 @@
"registry": "anthropics/skills",
"path": "skills/algorithmic-art",
"description": "Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.",
- "lastIndexed": 1779582297027
+ "lastIndexed": 1780187240570
},
{
"name": "brand-guidelines",
"registry": "anthropics/skills",
"path": "skills/brand-guidelines",
"description": "Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.",
- "lastIndexed": 1779582297010
+ "lastIndexed": 1780187240550
},
{
"name": "canvas-design",
"registry": "anthropics/skills",
"path": "skills/canvas-design",
"description": "Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.",
- "lastIndexed": 1779582297025
+ "lastIndexed": 1780187240631
},
{
"name": "claude-api",
"registry": "anthropics/skills",
"path": "skills/claude-api",
"description": "Build, debug, and optimize Claude API / Anthropic SDK apps. Apps built with this skill should include prompt caching. Also handles migrating existing Claude API code between Claude model versions (4.5 → 4.6, 4.6 → 4.7, retired-model replacements). TRIGGER when: code imports `anthropic`/`@anthropic-ai/sdk`; user asks for the Claude API, Anthropic SDK, or Managed Agents; user adds/modifies/tunes a Claude feature (caching, thinking, compaction, tool use, batch, files, citations, memory) or model (Opus/Sonnet/Haiku) in a file; questions about prompt caching / cache hit rate in an Anthropic SDK project. SKIP: file imports `openai`/other-provider SDK, filename like `*-openai.py`/`*-generic.py`, provider-neutral code, general programming/ML.",
- "lastIndexed": 1779582297027
+ "lastIndexed": 1780187240573
},
{
"name": "doc-coauthoring",
"registry": "anthropics/skills",
"path": "skills/doc-coauthoring",
"description": "Guide users through a structured workflow for co-authoring documentation. Use when user wants to write documentation, proposals, technical specs, decision docs, or similar structured content. This workflow helps users efficiently transfer context, refine content through iteration, and verify the doc works for readers. Trigger when user mentions writing docs, creating proposals, drafting specs, or similar documentation tasks.",
- "lastIndexed": 1779582297006
+ "lastIndexed": 1780187240626
},
{
"name": "docx",
"registry": "anthropics/skills",
"path": "skills/docx",
"description": "Use this skill whenever the user wants to create, read, edit, or manipulate Word documents (.docx files). Triggers include: any mention of 'Word doc', 'word document', '.docx', or requests to produce professional documents with formatting like tables of contents, headings, page numbers, or letterheads. Also use when extracting or reorganizing content from .docx files, inserting or replacing images in documents, performing find-and-replace in Word files, working with tracked changes or comments, or converting content into a polished Word document. If the user asks for a 'report', 'memo', 'letter', 'template', or similar deliverable as a Word or .docx file, use this skill. Do NOT use for PDFs, spreadsheets, Google Docs, or general coding tasks unrelated to document generation.",
- "lastIndexed": 1779582297009
+ "lastIndexed": 1780187240571
},
{
"name": "frontend-design",
"registry": "anthropics/skills",
"path": "skills/frontend-design",
"description": "Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.",
- "lastIndexed": 1779582297020
+ "lastIndexed": 1780187240563
},
{
"name": "internal-comms",
"registry": "anthropics/skills",
"path": "skills/internal-comms",
"description": "A set of resources to help me write all kinds of internal communications, using the formats that my company likes to use. Claude should use this skill whenever asked to write some sort of internal communications (status reports, leadership updates, 3P updates, company newsletters, FAQs, incident reports, project updates, etc.).",
- "lastIndexed": 1779582297008
+ "lastIndexed": 1780187240561
},
{
"name": "mcp-builder",
"registry": "anthropics/skills",
"path": "skills/mcp-builder",
"description": "Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).",
- "lastIndexed": 1779582297003
+ "lastIndexed": 1780187240569
},
{
"name": "pdf",
"registry": "anthropics/skills",
"path": "skills/pdf",
"description": "Use this skill whenever the user wants to do anything with PDF files. This includes reading or extracting text/tables from PDFs, combining or merging multiple PDFs into one, splitting PDFs apart, rotating pages, adding watermarks, creating new PDFs, filling PDF forms, encrypting/decrypting PDFs, extracting images, and OCR on scanned PDFs to make them searchable. If the user mentions a .pdf file or asks to produce one, use this skill.",
- "lastIndexed": 1779582297024
+ "lastIndexed": 1780187240562
},
{
"name": "pptx",
"registry": "anthropics/skills",
"path": "skills/pptx",
"description": "Use this skill any time a .pptx file is involved in any way — as input, output, or both. This includes: creating slide decks, pitch decks, or presentations; reading, parsing, or extracting text from any .pptx file (even if the extracted content will be used elsewhere, like in an email or summary); editing, modifying, or updating existing presentations; combining or splitting slide files; working with templates, layouts, speaker notes, or comments. Trigger whenever the user mentions \"deck,\" \"slides,\" \"presentation,\" or references a .pptx filename, regardless of what they plan to do with the content afterward. If a .pptx file needs to be opened, created, or touched, use this skill.",
- "lastIndexed": 1779582297030
+ "lastIndexed": 1780187240568
},
{
"name": "skill-creator",
"registry": "anthropics/skills",
"path": "skills/skill-creator",
"description": "Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, edit, or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.",
- "lastIndexed": 1779582297026
+ "lastIndexed": 1780187240572
},
{
"name": "slack-gif-creator",
"registry": "anthropics/skills",
"path": "skills/slack-gif-creator",
"description": "Knowledge and utilities for creating animated GIFs optimized for Slack. Provides constraints, validation tools, and animation concepts. Use when users request animated GIFs for Slack like \"make me a GIF of X doing Y for Slack.\"",
- "lastIndexed": 1779582297012
+ "lastIndexed": 1780187240571
},
{
"name": "theme-factory",
"registry": "anthropics/skills",
"path": "skills/theme-factory",
"description": "Toolkit for styling artifacts with a theme. These artifacts can be slides, docs, reportings, HTML landing pages, etc. There are 10 pre-set themes with colors/fonts that you can apply to any artifact that has been creating, or can generate a new theme on-the-fly.",
- "lastIndexed": 1779582297010
+ "lastIndexed": 1780187240562
},
{
"name": "web-artifacts-builder",
"registry": "anthropics/skills",
"path": "skills/web-artifacts-builder",
"description": "Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn/ui). Use for complex artifacts requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX artifacts.",
- "lastIndexed": 1779582297007
+ "lastIndexed": 1780187240570
},
{
"name": "webapp-testing",
"registry": "anthropics/skills",
"path": "skills/webapp-testing",
"description": "Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.",
- "lastIndexed": 1779582297020
+ "lastIndexed": 1780187240563
},
{
"name": "xlsx",
"registry": "anthropics/skills",
"path": "skills/xlsx",
"description": "Use this skill any time a spreadsheet file is the primary input or output. This means any task where the user wants to: open, read, edit, or fix an existing .xlsx, .xlsm, .csv, or .tsv file (e.g., adding columns, computing formulas, formatting, charting, cleaning messy data); create a new spreadsheet from scratch or from other data sources; or convert between tabular file formats. Trigger especially when the user references a spreadsheet file by name or path — even casually (like \"the xlsx in my downloads\") — and wants something done to it or produced from it. Also trigger for cleaning or restructuring messy tabular data files (malformed rows, misplaced headers, junk data) into proper spreadsheets. The deliverable must be a spreadsheet file. Do NOT trigger when the primary deliverable is a Word document, HTML report, standalone Python script, database pipeline, or Google Sheets API integration, even if tabular data is involved.",
- "lastIndexed": 1779582297024
+ "lastIndexed": 1780187240581
},
{
"name": "composition-patterns",
"registry": "vercel-labs/agent-skills",
"path": "skills/composition-patterns",
"description": "React composition patterns that scale. Use when refactoring components with boolean prop proliferation, building flexible component libraries, or designing reusable APIs. Triggers on tasks involving compound components, render props, context providers, or component architecture. Includes React 19 API changes.",
- "lastIndexed": 1779582297040
+ "lastIndexed": 1780187240577
},
{
"name": "deploy-to-vercel",
"registry": "vercel-labs/agent-skills",
"path": "skills/deploy-to-vercel",
"description": "Deploy applications and websites to Vercel. Use when the user requests deployment actions like \"deploy my app\", \"deploy and give me the link\", \"push this live\", or \"create a preview deployment\".",
- "lastIndexed": 1779582297059
+ "lastIndexed": 1780187240586
},
{
"name": "react-best-practices",
"registry": "vercel-labs/agent-skills",
"path": "skills/react-best-practices",
"description": "React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.",
- "lastIndexed": 1779582296986
+ "lastIndexed": 1780187240610
},
{
"name": "react-native-skills",
"registry": "vercel-labs/agent-skills",
"path": "skills/react-native-skills",
"description": "React Native and Expo best practices for building performant mobile apps. Use when building React Native components, optimizing list performance, implementing animations, or working with native modules. Triggers on tasks involving React Native, Expo, mobile performance, or native platform APIs.",
- "lastIndexed": 1779582297060
+ "lastIndexed": 1780187240631
},
{
"name": "react-view-transitions",
"registry": "vercel-labs/agent-skills",
"path": "skills/react-view-transitions",
"description": "Guide for implementing smooth, native-feeling animations using React's View Transition API (`` component, `addTransitionType`, and CSS view transition pseudo-elements). Use this skill whenever the user wants to add page transitions, animate route changes, create shared element animations, animate enter/exit of components, animate list reorder, implement directional (forward/back) navigation animations, or integrate view transitions in Next.js. Also use when the user mentions view transitions, `startViewTransition`, `ViewTransition`, transition types, or asks about animating between UI states in React without third-party animation libraries.",
- "lastIndexed": 1779582297068
+ "lastIndexed": 1780187240622
},
{
"name": "vercel-cli-with-tokens",
"registry": "vercel-labs/agent-skills",
"path": "skills/vercel-cli-with-tokens",
"description": "Deploy and manage projects on Vercel using token-based authentication. Use when working with Vercel CLI using access tokens rather than interactive login — e.g. \"deploy to vercel\", \"set up vercel\", \"add environment variables to vercel\".",
- "lastIndexed": 1779582297067
+ "lastIndexed": 1780187240624
},
{
"name": "vercel-optimize",
"registry": "vercel-labs/agent-skills",
"path": "skills/vercel-optimize",
"description": "Use for Vercel cost and performance optimization on deployed projects, especially Next.js, SvelteKit, Nuxt, and limited Astro apps. Collect Vercel metrics, usage, project config, and code scan results first; investigate only metric-backed candidates; produce ranked recommendations grounded in verified files and version-aware Vercel/framework docs. Trigger for Vercel bill reduction, slow or expensive routes, caching opportunities, Function Invocations, Build Minutes, Fast Data Transfer, Core Web Vitals, Bot Management, Fluid compute, or cost breakdown requests.",
- "lastIndexed": 1779582297056
+ "lastIndexed": 1780187240645
},
{
"name": "web-design-guidelines",
"registry": "vercel-labs/agent-skills",
"path": "skills/web-design-guidelines",
"description": "Review UI code for Web Interface Guidelines compliance. Use when asked to \"review my UI\", \"check accessibility\", \"audit design\", \"review UX\", or \"check my site against best practices\".",
- "lastIndexed": 1779582297045
+ "lastIndexed": 1780187240609
},
{
"name": "remotion",
"registry": "remotion-dev/skills",
"path": "skills/remotion",
"description": "Best practices for Remotion - Video creation in React",
- "lastIndexed": 1779582296969
+ "lastIndexed": 1780187240544
},
{
"name": "supabase-postgres-best-practices",
"registry": "supabase/agent-skills",
"path": "skills/supabase-postgres-best-practices",
"description": "Postgres performance optimization and best practices from Supabase. Use this skill when writing, reviewing, or optimizing Postgres queries, schema designs, or database configurations.",
- "lastIndexed": 1779582297013
+ "lastIndexed": 1780187240557
},
{
"name": "supabase",
"registry": "supabase/agent-skills",
"path": "skills/supabase",
"description": "Use when doing ANY task involving Supabase. Triggers: Supabase products (Database, Auth, Edge Functions, Realtime, Storage, Vectors, Cron, Queues); client libraries and SSR integrations (supabase-js, @supabase/ssr) in Next.js, React, SvelteKit, Astro, Remix; auth issues (login, logout, sessions, JWT, cookies, getSession, getUser, getClaims, RLS); Supabase CLI or MCP server; schema changes, migrations, security audits, Postgres extensions (pg_graphql, pg_cron, pg_vector).",
- "lastIndexed": 1779582297023
+ "lastIndexed": 1780187240568
},
{
"name": "brainstorming",
"registry": "obra/superpowers",
"path": "skills/brainstorming",
"description": "You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation.",
- "lastIndexed": 1779582297000
+ "lastIndexed": 1780187240585
},
{
"name": "dispatching-parallel-agents",
"registry": "obra/superpowers",
"path": "skills/dispatching-parallel-agents",
"description": "Use when facing 2+ independent tasks that can be worked on without shared state or sequential dependencies",
- "lastIndexed": 1779582297014
+ "lastIndexed": 1780187240613
},
{
"name": "executing-plans",
"registry": "obra/superpowers",
"path": "skills/executing-plans",
"description": "Use when you have a written implementation plan to execute in a separate session with review checkpoints",
- "lastIndexed": 1779582297008
+ "lastIndexed": 1780187240597
},
{
"name": "finishing-a-development-branch",
"registry": "obra/superpowers",
"path": "skills/finishing-a-development-branch",
"description": "Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup",
- "lastIndexed": 1779582296997
+ "lastIndexed": 1780187240585
},
{
"name": "receiving-code-review",
"registry": "obra/superpowers",
"path": "skills/receiving-code-review",
"description": "Use when receiving code review feedback, before implementing suggestions, especially if feedback seems unclear or technically questionable - requires technical rigor and verification, not performative agreement or blind implementation",
- "lastIndexed": 1779582297009
+ "lastIndexed": 1780187240604
},
{
"name": "requesting-code-review",
"registry": "obra/superpowers",
"path": "skills/requesting-code-review",
"description": "Use when completing tasks, implementing major features, or before merging to verify work meets requirements",
- "lastIndexed": 1779582297009
+ "lastIndexed": 1780187240601
},
{
"name": "subagent-driven-development",
"registry": "obra/superpowers",
"path": "skills/subagent-driven-development",
"description": "Use when executing implementation plans with independent tasks in the current session",
- "lastIndexed": 1779582296991
+ "lastIndexed": 1780187240605
},
{
"name": "systematic-debugging",
"registry": "obra/superpowers",
"path": "skills/systematic-debugging",
"description": "Use when encountering any bug, test failure, or unexpected behavior, before proposing fixes",
- "lastIndexed": 1779582296999
+ "lastIndexed": 1780187240603
},
{
"name": "test-driven-development",
"registry": "obra/superpowers",
"path": "skills/test-driven-development",
"description": "Use when implementing any feature or bugfix, before writing implementation code",
- "lastIndexed": 1779582296991
+ "lastIndexed": 1780187240604
},
{
"name": "using-git-worktrees",
"registry": "obra/superpowers",
"path": "skills/using-git-worktrees",
"description": "Use when starting feature work that needs isolation from current workspace or before executing implementation plans - ensures an isolated workspace exists via native tools or git worktree fallback",
- "lastIndexed": 1779582296992
+ "lastIndexed": 1780187240603
},
{
"name": "using-superpowers",
"registry": "obra/superpowers",
"path": "skills/using-superpowers",
"description": "Use when starting any conversation - establishes how to find and use skills, requiring Skill tool invocation before ANY response including clarifying questions",
- "lastIndexed": 1779582296984
+ "lastIndexed": 1780187240700
},
{
"name": "verification-before-completion",
"registry": "obra/superpowers",
"path": "skills/verification-before-completion",
"description": "Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always",
- "lastIndexed": 1779582296989
+ "lastIndexed": 1780187240604
},
{
"name": "writing-plans",
"registry": "obra/superpowers",
"path": "skills/writing-plans",
"description": "Use when you have a spec or requirements for a multi-step task, before touching code",
- "lastIndexed": 1779582296999
+ "lastIndexed": 1780187240599
},
{
"name": "writing-skills",
"registry": "obra/superpowers",
"path": "skills/writing-skills",
"description": "Use when creating new skills, editing existing skills, or verifying skills work before deployment",
- "lastIndexed": 1779582297013
+ "lastIndexed": 1780187240604
},
{
"name": "agent-md-refactor",
"registry": "softaworks/agent-toolkit",
"path": "skills/agent-md-refactor",
"description": "Refactor bloated AGENTS.md, CLAUDE.md, or similar agent instruction files to follow progressive disclosure principles. Splits monolithic files into organized, linked documentation.",
- "lastIndexed": 1779582297477
+ "lastIndexed": 1780187241003
},
{
"name": "backend-to-frontend-handoff-docs",
"registry": "softaworks/agent-toolkit",
"path": "skills/backend-to-frontend-handoff-docs",
"description": "Create API handoff documentation for frontend developers. Use when backend work is complete and needs to be documented for frontend integration, or user says 'create handoff', 'document API', 'frontend handoff', or 'API documentation'.",
- "lastIndexed": 1779582297476
+ "lastIndexed": 1780187241004
},
{
"name": "c4-architecture",
"registry": "softaworks/agent-toolkit",
"path": "skills/c4-architecture",
"description": "Generate architecture documentation using C4 model Mermaid diagrams. Use when asked to create architecture diagrams, document system architecture, visualize software structure, create C4 diagrams, or generate context/container/component/deployment diagrams. Triggers include \"architecture diagram\", \"C4 diagram\", \"system context\", \"container diagram\", \"component diagram\", \"deployment diagram\", \"document architecture\", \"visualize architecture\".",
- "lastIndexed": 1779582297471
+ "lastIndexed": 1780187241003
},
{
"name": "codex",
"registry": "softaworks/agent-toolkit",
"path": "skills/codex",
"description": "Use when the user asks to run Codex CLI (codex exec, codex resume) or references OpenAI Codex for code analysis, refactoring, or automated editing. Uses GPT-5.2 by default for state-of-the-art software engineering.",
- "lastIndexed": 1779582297475
+ "lastIndexed": 1780187240992
},
{
"name": "command-creator",
"registry": "softaworks/agent-toolkit",
"path": "skills/command-creator",
"description": "This skill should be used when creating a Claude Code slash command. Use when users ask to \"create a command\", \"make a slash command\", \"add a command\", or want to document a workflow as a reusable command. Essential for creating optimized, agent-executable slash commands with proper structure and best practices.",
- "lastIndexed": 1779582297477
+ "lastIndexed": 1780187241008
},
{
"name": "commit-work",
"registry": "softaworks/agent-toolkit",
"path": "skills/commit-work",
"description": "Create high-quality git commits: review/stage intended changes, split into logical commits, and write clear commit messages (including Conventional Commits). Use when the user asks to commit, craft a commit message, stage changes, or split work into multiple commits.",
- "lastIndexed": 1779582297469
+ "lastIndexed": 1780187241043
},
{
"name": "crafting-effective-readmes",
"registry": "softaworks/agent-toolkit",
"path": "skills/crafting-effective-readmes",
"description": "Use when writing or improving README files. Not all READMEs are the same — provides templates and guidance matched to your audience and project type.",
- "lastIndexed": 1779582297469
+ "lastIndexed": 1780187240993
},
{
"name": "daily-meeting-update",
"registry": "softaworks/agent-toolkit",
"path": "skills/daily-meeting-update",
"description": "Interactive daily standup/meeting update generator. Use when user says 'daily', 'standup', 'scrum update', 'status update', 'what did I do yesterday', 'prepare for meeting', 'morning update', or 'team sync'. Pulls activity from GitHub, Jira, and Claude Code session history. Conducts 4-question interview (yesterday, today, blockers, discussion topics) and generates formatted Markdown update.",
- "lastIndexed": 1779582297483
+ "lastIndexed": 1780187241011
},
{
"name": "database-schema-designer",
"registry": "softaworks/agent-toolkit",
"path": "skills/database-schema-designer",
"description": "Design robust, scalable database schemas for SQL and NoSQL databases. Provides normalization guidelines, indexing strategies, migration patterns, constraint design, and performance optimization. Ensures data integrity, query performance, and maintainable data models.",
- "lastIndexed": 1779582297490
+ "lastIndexed": 1780187241016
},
{
"name": "datadog-cli",
"registry": "softaworks/agent-toolkit",
"path": "skills/datadog-cli",
"description": "Datadog CLI for searching logs, querying metrics, tracing requests, and managing dashboards. Use this when debugging production issues or working with Datadog observability.",
- "lastIndexed": 1779582297470
+ "lastIndexed": 1780187241005
},
{
"name": "dependency-updater",
"registry": "softaworks/agent-toolkit",
"path": "skills/dependency-updater",
"description": "Smart dependency management for any language. Auto-detects project type, applies safe updates automatically, prompts for major versions, diagnoses and fixes dependency issues.",
- "lastIndexed": 1779582297477
+ "lastIndexed": 1780187241008
},
{
"name": "design-system-starter",
"registry": "softaworks/agent-toolkit",
"path": "skills/design-system-starter",
"description": "Create and evolve design systems with design tokens, component architecture, accessibility guidelines, and documentation templates. Ensures consistent, scalable, and accessible UI across products.",
- "lastIndexed": 1779582297587
+ "lastIndexed": 1780187241017
},
{
"name": "difficult-workplace-conversations",
"registry": "softaworks/agent-toolkit",
"path": "skills/difficult-workplace-conversations",
"description": "Structured approach to workplace conflicts, performance discussions, and challenging feedback using preparation-delivery-followup framework. Use when preparing for tough conversations, addressing conflicts, giving critical feedback, or navigating sensitive workplace discussions.",
- "lastIndexed": 1779582297476
+ "lastIndexed": 1780187241012
},
{
"name": "domain-name-brainstormer",
"registry": "softaworks/agent-toolkit",
"path": "skills/domain-name-brainstormer",
"description": "Generates creative domain name ideas for your project and checks availability across multiple TLDs (.com, .io, .dev, .ai, etc.). Saves hours of brainstorming and manual checking.",
- "lastIndexed": 1779582297475
+ "lastIndexed": 1780187241004
},
{
"name": "draw-io",
"registry": "softaworks/agent-toolkit",
"path": "skills/draw-io",
"description": "draw.io diagram creation, editing, and review. Use for .drawio XML editing, PNG conversion, layout adjustment, and AWS icon usage.",
- "lastIndexed": 1779582297478
+ "lastIndexed": 1780187241008
},
{
"name": "excalidraw",
"registry": "softaworks/agent-toolkit",
"path": "skills/excalidraw",
"description": "Use when working with *.excalidraw or *.excalidraw.json files, user mentions diagrams/flowcharts, or requests architecture visualization - delegates all Excalidraw operations to subagents to prevent context exhaustion from verbose JSON (single files: 4k-22k tokens, can exceed read limits)",
- "lastIndexed": 1779582297497
+ "lastIndexed": 1780187241003
},
{
"name": "feedback-mastery",
"registry": "softaworks/agent-toolkit",
"path": "skills/feedback-mastery",
"description": "Navigate difficult conversations and deliver constructive feedback using structured frameworks. Covers the Preparation-Delivery-Follow-up model and Situation-Behavior-Impact (SBI) feedback technique. Use when preparing for difficult conversations, giving feedback, or managing conflicts.",
- "lastIndexed": 1779582297491
+ "lastIndexed": 1780187241007
},
{
"name": "frontend-to-backend-requirements",
"registry": "softaworks/agent-toolkit",
"path": "skills/frontend-to-backend-requirements",
"description": "Document frontend data needs for backend developers. Use when frontend needs to communicate API requirements to backend, or user says 'backend requirements', 'what data do I need', 'API requirements', or is describing data needs for a UI.",
- "lastIndexed": 1779582297509
+ "lastIndexed": 1780187240993
},
{
"name": "game-changing-features",
"registry": "softaworks/agent-toolkit",
"path": "skills/game-changing-features",
"description": "Find 10x product opportunities and high-leverage improvements. Use when user wants strategic product thinking, mentions '10x', wants to find high-impact features, or says 'what would make this 10x better', 'product strategy', or 'what should we build next'.",
- "lastIndexed": 1779582297499
+ "lastIndexed": 1780187241009
},
{
"name": "gemini",
"registry": "softaworks/agent-toolkit",
"path": "skills/gemini",
"description": "Use when the user asks to run Gemini CLI for code review, plan review, or big context (>200k) processing. Ideal for comprehensive analysis requiring large context windows. Uses Gemini 3 Pro by default for state-of-the-art reasoning and coding.",
- "lastIndexed": 1779582297495
+ "lastIndexed": 1780187241011
},
{
"name": "gepetto",
"registry": "softaworks/agent-toolkit",
"path": "skills/gepetto",
"description": "Creates detailed, sectionized implementation plans through research, stakeholder interviews, and multi-LLM review. Use when planning features that need thorough pre-implementation analysis.",
- "lastIndexed": 1779582297535
+ "lastIndexed": 1780187241008
},
{
"name": "humanizer",
"registry": "softaworks/agent-toolkit",
"path": "skills/humanizer",
"description": "Remove signs of AI-generated writing from text. Use when editing or reviewing\ntext to make it sound more natural and human-written. Based on Wikipedia's\ncomprehensive \"Signs of AI writing\" guide. Detects and fixes patterns including:\ninflated symbolism, promotional language, superficial -ing analyses, vague\nattributions, em dash overuse, rule of three, AI vocabulary words, negative\nparallelisms, and excessive conjunctive phrases.\n\nCredits: Original skill by @blader - https://github.com/blader/humanizer",
- "lastIndexed": 1779582297536
+ "lastIndexed": 1780187241015
},
{
"name": "jira",
"registry": "softaworks/agent-toolkit",
"path": "skills/jira",
"description": "Use when the user mentions Jira issues (e.g., \"PROJ-123\"), asks about tickets, wants to create/view/update issues, check sprint status, or manage their Jira workflow. Triggers on keywords like \"jira\", \"issue\", \"ticket\", \"sprint\", \"backlog\", or issue key patterns.",
- "lastIndexed": 1779582297535
+ "lastIndexed": 1780187241015
},
{
"name": "lesson-learned",
"registry": "softaworks/agent-toolkit",
"path": "skills/lesson-learned",
"description": "Analyze recent code changes via git history and extract software engineering lessons. Use when the user asks 'what is the lesson here?', 'what can I learn from this?', 'engineering takeaway', 'what did I just learn?', 'reflect on this code', or wants to extract principles from recent work.",
- "lastIndexed": 1779582297511
+ "lastIndexed": 1780187241028
},
{
"name": "marp-slide",
"registry": "softaworks/agent-toolkit",
"path": "skills/marp-slide",
"description": "Create professional Marp presentation slides with 7 beautiful themes (default, minimal, colorful, dark, gradient, tech, business). Use when users request slide creation, presentations, or Marp documents. Supports custom themes, image layouts, and \"make it look good\" requests with automatic quality improvements.",
- "lastIndexed": 1779582297517
+ "lastIndexed": 1780187241012
},
{
"name": "meme-factory",
"registry": "softaworks/agent-toolkit",
"path": "skills/meme-factory",
"description": "Generate memes using the memegen.link API. Use when users request memes, want to add humor to content, or need visual aids for social media. Supports 100+ popular templates with custom text and styling.",
- "lastIndexed": 1779582297513
+ "lastIndexed": 1780187241012
},
{
"name": "mermaid-diagrams",
"registry": "softaworks/agent-toolkit",
"path": "skills/mermaid-diagrams",
"description": "Comprehensive guide for creating software diagrams using Mermaid syntax. Use when users need to create, visualize, or document software through diagrams including class diagrams (domain modeling, object-oriented design), sequence diagrams (application flows, API interactions, code execution), flowcharts (processes, algorithms, user journeys), entity relationship diagrams (database schemas), C4 architecture diagrams (system context, containers, components), state diagrams, git graphs, pie charts, gantt charts, or any other diagram type. Triggers include requests to \"diagram\", \"visualize\", \"model\", \"map out\", \"show the flow\", or when explaining system architecture, database design, code structure, or user/application flows.",
- "lastIndexed": 1779582297497
+ "lastIndexed": 1780187241008
},
{
"name": "mui",
"registry": "softaworks/agent-toolkit",
"path": "skills/mui",
"description": "Material-UI v7 component library patterns including sx prop styling, theme integration, responsive design, and MUI-specific hooks. Use when working with MUI components, styling with sx prop, theme customization, or MUI utilities.",
- "lastIndexed": 1779582297505
+ "lastIndexed": 1780187241003
},
{
"name": "naming-analyzer",
"registry": "softaworks/agent-toolkit",
"path": "skills/naming-analyzer",
"description": "Suggest better variable, function, and class names based on context and conventions.",
- "lastIndexed": 1779582297542
+ "lastIndexed": 1780187241009
},
{
"name": "openapi-to-typescript",
"registry": "softaworks/agent-toolkit",
"path": "skills/openapi-to-typescript",
"description": "Converts OpenAPI 3.0 JSON/YAML to TypeScript interfaces and type guards. This skill should be used when the user asks to generate types from OpenAPI, convert schema to TS, create API interfaces, or generate TypeScript types from an API specification.",
- "lastIndexed": 1779582297584
+ "lastIndexed": 1780187241005
},
{
"name": "perplexity",
"registry": "softaworks/agent-toolkit",
"path": "skills/perplexity",
"description": "Web search and research using Perplexity AI. Use when user says \"search\", \"find\", \"look up\", \"ask\", \"research\", or \"what's the latest\" for generic queries. NOT for library/framework docs (use Context7) or workspace questions.",
- "lastIndexed": 1779582297500
+ "lastIndexed": 1780187241012
},
{
"name": "plugin-forge",
"registry": "softaworks/agent-toolkit",
"path": "skills/plugin-forge",
"description": "Create and manage Claude Code plugins with proper structure, manifests, and marketplace integration. Use when creating plugins for a marketplace, adding plugin components (commands, agents, hooks), bumping plugin versions, or working with plugin.json/marketplace.json manifests.",
- "lastIndexed": 1779582297519
+ "lastIndexed": 1780187241014
},
{
"name": "professional-communication",
"registry": "softaworks/agent-toolkit",
"path": "skills/professional-communication",
"description": "Guide technical communication for software developers. Covers email structure, team messaging etiquette, meeting agendas, and adapting messages for technical vs non-technical audiences. Use when drafting professional messages, preparing meeting communications, or improving written communication.",
- "lastIndexed": 1779582297519
+ "lastIndexed": 1780187241017
},
{
"name": "qa-test-planner",
"registry": "softaworks/agent-toolkit",
"path": "skills/qa-test-planner",
"description": "Generate comprehensive test plans, manual test cases, regression test suites, and bug reports for QA engineers. Includes Figma MCP integration for design validation.",
- "lastIndexed": 1779582297543
+ "lastIndexed": 1780187241018
},
{
"name": "react-dev",
"registry": "softaworks/agent-toolkit",
"path": "skills/react-dev",
"description": "This skill should be used when building React components with TypeScript, typing hooks, handling events, or when React TypeScript, React 19, Server Components are mentioned. Covers type-safe patterns for React 18-19 including generic components, proper event typing, and routing integration (TanStack Router, React Router).",
- "lastIndexed": 1779582297530
+ "lastIndexed": 1780187241017
},
{
"name": "react-useeffect",
"registry": "softaworks/agent-toolkit",
"path": "skills/react-useeffect",
"description": "React useEffect best practices from official docs. Use when writing/reviewing useEffect, useState for derived values, data fetching, or state synchronization. Teaches when NOT to use Effect and better alternatives.",
- "lastIndexed": 1779582297526
+ "lastIndexed": 1780187241009
},
{
"name": "reducing-entropy",
"registry": "softaworks/agent-toolkit",
"path": "skills/reducing-entropy",
"description": "Manual-only skill for minimizing total codebase size. Only activate when explicitly requested by user. Measures success by final code amount, not effort. Bias toward deletion.",
- "lastIndexed": 1779582297518
+ "lastIndexed": 1780187241010
},
{
"name": "requirements-clarity",
"registry": "softaworks/agent-toolkit",
"path": "skills/requirements-clarity",
"description": "Clarify ambiguous requirements through focused dialogue before implementation. Use when requirements are unclear, features are complex (>2 days), or involve cross-team coordination. Ask two core questions - Why? (YAGNI check) and Simpler? (KISS check) - to ensure clarity before coding.",
- "lastIndexed": 1779582297509
+ "lastIndexed": 1780187241014
},
{
"name": "session-handoff",
"registry": "softaworks/agent-toolkit",
"path": "skills/session-handoff",
"description": "Creates comprehensive handoff documents for seamless AI agent session transfers. Triggered when: (1) user requests handoff/memory/context save, (2) context window approaches capacity, (3) major task milestone completed, (4) work session ending, (5) user says 'save state', 'create handoff', 'I need to pause', 'context is getting full', (6) resuming work with 'load handoff', 'resume from', 'continue where we left off'. Proactively suggests handoffs after substantial work (multiple file edits, complex debugging, architecture decisions). Solves long-running agent context exhaustion by enabling fresh agents to continue with zero ambiguity.",
- "lastIndexed": 1779582297529
+ "lastIndexed": 1780187241066
},
{
"name": "ship-learn-next",
"registry": "softaworks/agent-toolkit",
"path": "skills/ship-learn-next",
"description": "Transform learning content (like YouTube transcripts, articles, tutorials) into actionable implementation plans using the Ship-Learn-Next framework. Use when user wants to turn advice, lessons, or educational content into concrete action steps, reps, or a learning quest.",
- "lastIndexed": 1779582297512
+ "lastIndexed": 1780187241016
},
{
"name": "skill-judge",
"registry": "softaworks/agent-toolkit",
"path": "skills/skill-judge",
"description": "Evaluate Agent Skill design quality against official specifications and best practices. Use when reviewing, auditing, or improving SKILL.md files and skill packages. Provides multi-dimensional scoring and actionable improvement suggestions.",
- "lastIndexed": 1779582297537
+ "lastIndexed": 1780187241018
},
{
"name": "web-to-markdown",
"registry": "softaworks/agent-toolkit",
"path": "skills/web-to-markdown",
"description": "Use ONLY when the user explicitly says: 'use the skill web-to-markdown ...' (or 'use a skill web-to-markdown ...'). Converts webpage URLs to clean Markdown by calling the local web2md CLI (Puppeteer + Readability), suitable for JS-rendered pages.",
- "lastIndexed": 1779582297532
+ "lastIndexed": 1780187241093
},
{
"name": "writing-clearly-and-concisely",
"registry": "softaworks/agent-toolkit",
"path": "skills/writing-clearly-and-concisely",
"description": "Use when writing prose humans will read—documentation, commit messages, error messages, explanations, reports, or UI text. Applies Strunk's timeless rules for clearer, stronger, more professional writing.",
- "lastIndexed": 1779582297535
+ "lastIndexed": 1780187241012
},
{
"name": "agent-orchestration",
"registry": "codeaholicguy/ai-devkit",
"path": "skills/agent-orchestration",
"description": "AI DevKit · Proactively orchestrate running AI agents — scan statuses, assess progress, send next instructions, and coordinate multi-agent workflows. Use when users ask to manage agents, orchestrate work across agents, or check on agent progress.",
- "lastIndexed": 1779582297386
+ "lastIndexed": 1780187241002
},
{
"name": "dev-lifecycle",
"registry": "codeaholicguy/ai-devkit",
"path": "skills/dev-lifecycle",
"description": "AI DevKit · Structured SDLC workflow with 8 phases — requirements, design review, planning, implementation, testing, and code review. Use when the user wants to build a feature end-to-end, or run any individual phase (new requirement, review requirements, review design, execute plan, update planning, check implementation, write tests, code review).",
- "lastIndexed": 1779582297396
+ "lastIndexed": 1780187241002
},
{
"name": "document-code",
"registry": "codeaholicguy/ai-devkit",
"path": "skills/document-code",
"description": "AI DevKit · Document a code entry point with structured analysis, dependency mapping, and saved knowledge docs. Use when users ask to document, understand, or map code for a module, file, folder, function, or API.",
- "lastIndexed": 1779582297399
+ "lastIndexed": 1780187240991
},
{
"name": "memory",
"registry": "codeaholicguy/ai-devkit",
"path": "skills/memory",
"description": "AI DevKit · Use the memory CLI as a durable knowledge layer. Search before non-trivial work, store verified reusable knowledge, update stale entries, and avoid saving transcripts, secrets, or one-off task progress.",
- "lastIndexed": 1779582297397
+ "lastIndexed": 1780187240992
},
{
"name": "security-review",
"registry": "codeaholicguy/ai-devkit",
"path": "skills/security-review",
"description": "AI DevKit · Review code, skills, and prompts for security vulnerabilities — OWASP Top 10, prompt injection, business logic flaws, and insecure defaults. Use when reviewing PRs, auditing modules, reviewing AI skills/prompts, or preparing for release.",
- "lastIndexed": 1779582297395
+ "lastIndexed": 1780187240993
},
{
"name": "simplify-implementation",
"registry": "codeaholicguy/ai-devkit",
"path": "skills/simplify-implementation",
"description": "AI DevKit · Analyze and simplify existing implementations to reduce complexity, improve maintainability, and enhance scalability. Use when users ask to simplify code, reduce complexity, refactor for readability, clean up implementations, improve maintainability, reduce technical debt, or make code easier to understand.",
- "lastIndexed": 1779582297387
+ "lastIndexed": 1780187240991
},
{
"name": "structured-debug",
"registry": "codeaholicguy/ai-devkit",
"path": "skills/structured-debug",
"description": "AI DevKit · Guide structured debugging before code changes by clarifying expected behavior, reproducing issues, identifying likely root causes, and agreeing on a fix plan with validation steps. Use when users ask to debug bugs, investigate regressions, triage incidents, diagnose failing behavior, handle failing tests, analyze production incidents, investigate error spikes, or run root cause analysis (RCA).",
- "lastIndexed": 1779582297396
+ "lastIndexed": 1780187240985
},
{
"name": "tdd",
"registry": "codeaholicguy/ai-devkit",
"path": "skills/tdd",
"description": "AI DevKit · Test-driven development — write a failing test before writing production code. Use when implementing new functionality, adding behavior, or fixing bugs during active development.",
- "lastIndexed": 1779582297388
+ "lastIndexed": 1780187240991
},
{
"name": "technical-writer",
"registry": "codeaholicguy/ai-devkit",
"path": "skills/technical-writer",
"description": "AI DevKit · Review and improve documentation for novice users. Use when users ask to review docs, improve documentation, audit README files, evaluate API docs, review guides, or improve technical writing.",
- "lastIndexed": 1779582297394
+ "lastIndexed": 1780187240985
},
{
"name": "verify",
"registry": "codeaholicguy/ai-devkit",
"path": "skills/verify",
"description": "AI DevKit · Enforce evidence-based completion claims — require fresh command output before reporting success. Use when completing any task, fixing a bug, finishing a phase, running tests, building, deploying, or making any \"it works\" claim.",
- "lastIndexed": 1779582297395
+ "lastIndexed": 1780187240991
},
{
"name": "antfu",
"registry": "antfu/skills",
"path": "skills/antfu",
"description": "Anthony Fu's opinionated tooling and conventions for JavaScript/TypeScript projects. Use when setting up new projects, configuring ESLint/Prettier alternatives, monorepos, library publishing, or when the user mentions Anthony Fu's preferences.",
- "lastIndexed": 1779582297467
+ "lastIndexed": 1780187241070
},
{
"name": "nuxt",
"registry": "antfu/skills",
"path": "skills/nuxt",
"description": "Nuxt full-stack Vue framework with SSR, auto-imports, and file-based routing. Use when working with Nuxt apps, server routes, useFetch, middleware, or hybrid rendering.",
- "lastIndexed": 1779582297461
+ "lastIndexed": 1780187241054
},
{
"name": "pinia",
"registry": "antfu/skills",
"path": "skills/pinia",
"description": "Pinia official Vue state management library, type-safe and extensible. Use when defining stores, working with state/getters/actions, or implementing store patterns in Vue apps.",
- "lastIndexed": 1779582297461
+ "lastIndexed": 1780187241060
},
{
"name": "pnpm",
"registry": "antfu/skills",
"path": "skills/pnpm",
"description": "Node.js package manager with strict dependency resolution. Use when running pnpm specific commands, configuring workspaces, or managing dependencies with catalogs, patches, or overrides.",
- "lastIndexed": 1779582297468
+ "lastIndexed": 1780187241084
},
{
"name": "slidev",
"registry": "antfu/skills",
"path": "skills/slidev",
"description": "Create and present web-based slidedecks for developers using Slidev with Markdown, Vue components, code highlighting, animations, and interactive features. Use when building technical presentations, conference talks, code walkthroughs, teaching materials, or developer decks.",
- "lastIndexed": 1779582297468
+ "lastIndexed": 1780187241141
},
{
"name": "tsdown",
"registry": "antfu/skills",
"path": "skills/tsdown",
"description": "Bundle TypeScript and JavaScript libraries with blazing-fast speed powered by Rolldown. Use when building libraries, generating type declarations, bundling for multiple formats, or migrating from tsup.",
- "lastIndexed": 1779582297474
+ "lastIndexed": 1780187241065
},
{
"name": "turborepo",
"registry": "antfu/skills",
"path": "skills/turborepo",
"description": "Turborepo monorepo build system guidance. Triggers on: turbo.json, task pipelines,\ndependsOn, caching, remote cache, the \"turbo\" CLI, --filter, --affected, CI optimization, environment\nvariables, internal packages, monorepo structure/best practices, and boundaries.\n\nUse when user: configures tasks/workflows/pipelines, creates packages, sets up\nmonorepo, shares code between apps, runs changed/affected packages, debugs cache,\nor has apps/packages directories.",
- "lastIndexed": 1779582297490
+ "lastIndexed": 1780187241070
},
{
"name": "unocss",
"registry": "antfu/skills",
"path": "skills/unocss",
"description": "UnoCSS instant atomic CSS engine, superset of Tailwind CSS. Use when configuring UnoCSS, writing utility rules, shortcuts, or working with presets like Wind, Icons, Attributify.",
- "lastIndexed": 1779582297467
+ "lastIndexed": 1780187241073
},
{
"name": "vite",
"registry": "antfu/skills",
"path": "skills/vite",
"description": "Vite build tool configuration, plugin API, SSR, and Vite 8 Rolldown migration. Use when working with Vite projects, vite.config.ts, Vite plugins, or building libraries/SSR apps with Vite.",
- "lastIndexed": 1779582297469
+ "lastIndexed": 1780187241065
},
{
"name": "vitepress",
"registry": "antfu/skills",
"path": "skills/vitepress",
"description": "VitePress static site generator powered by Vite and Vue. Use when building documentation sites, configuring themes, or writing Markdown with Vue components.",
- "lastIndexed": 1779582297460
+ "lastIndexed": 1780187241064
},
{
"name": "vitest",
"registry": "antfu/skills",
"path": "skills/vitest",
"description": "Vitest fast unit testing framework powered by Vite with Jest-compatible API. Use when writing tests, mocking, configuring coverage, or working with test filtering and fixtures.",
- "lastIndexed": 1779582297460
+ "lastIndexed": 1780187241153
},
{
"name": "vue-best-practices",
"registry": "antfu/skills",
"path": "skills/vue-best-practices",
"description": "MUST be used for Vue.js tasks. Strongly recommends Composition API with `