Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions docs/ai/design/2026-05-31-feature-agent-rename.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
---
phase: design
title: System Design & Architecture
description: Define the technical architecture, components, and data models
---

# System Design & Architecture

## Architecture Overview

```mermaid
graph TD
CLI["CLI: agent rename <current> <new>"]
Validate["Validate new name (NAME_REGEX)"]
Registry["AgentRegistry.rename(current, new)"]
FS["~/.ai-devkit/agents.json (atomic write)"]

CLI --> Validate
Validate -->|valid| Registry
Validate -->|invalid| ErrFormat["Error: invalid name format"]
Registry -->|current not found| ErrNotFound["Error: agent not found"]
Registry -->|new name in use (live)| ErrConflict["Error: name already in use"]
Registry -->|success| FS
FS --> Success["ui.success: renamed"]
```

## Data Models

`RegistryEntry` (existing, unchanged):
```ts
interface RegistryEntry {
name: string; // ← this field is updated
type: AgentType;
pid: number;
tmuxSession: string; // ← NOT updated (registry-only rename)
cwd: string;
startedAt: string;
sessionId: string;
sessionFilePath: string;
}
```

## API Design

### `AgentRegistry.rename(currentName: string, newName: string): void`

Added to `packages/agent-manager/src/utils/AgentRegistry.ts`.

Behaviour:
1. Read the registry file.
2. Find entry with `name === currentName`. If not found, throw `RenameNotFoundError`.
3. Prune stale entries before conflict check.
4. Check whether any remaining entry has `name === newName`. If found and alive, throw `RenameConflictError`.
5. Update the matched entry's `name` to `newName`. Write atomically.

Two new error classes (in `AgentRegistry.ts`):
```ts
export class RenameNotFoundError extends Error {
constructor(public agentName: string) { ... }
}
export class RenameConflictError extends Error {
constructor(public agentName: string) { ... }
}
```

### CLI: `agent rename <current-name> <new-name>`

Added in `registerAgentCommand()` in `packages/cli/src/commands/agent.ts`.

```
ai-devkit agent rename <current-name> <new-name>
```

Validation order:
1. Validate `<new-name>` against `NAME_REGEX` → exit 1 with format hint.
2. Short-circuit if `<current-name> === <new-name>` → success no-op message.
3. Call `AgentRegistry.default().rename(currentName, newName)`.
4. Catch `RenameNotFoundError` → `ui.error` + exit 1.
5. Catch `RenameConflictError` → `ui.error` + exit 1.
6. On success → `ui.success("Agent \"<old>\" renamed to \"<new>\".")`.

## Component Breakdown

| Component | File | Change |
|-----------|------|--------|
| `AgentRegistry` | `packages/agent-manager/src/utils/AgentRegistry.ts` | Add `rename()`, `RenameNotFoundError`, `RenameConflictError` |
| `agent.ts` CLI | `packages/cli/src/commands/agent.ts` | Add `agent rename` subcommand |
| `index.ts` (agent-manager) | `packages/agent-manager/src/index.ts` | Export new error classes |

## Design Decisions

**Registry-only rename (no tmux session rename):** Per user decision. Simpler implementation, no dependency on a live tmux session. The `tmuxSession` field retains the original session name, so `tmux attach -t <original>` still works.

**Prune before conflict check:** Matches the pattern used in `startAgent`. Prevents a stale dead entry from blocking a valid rename.

**Same-name short-circuit in CLI (not registry):** Keeps `AgentRegistry.rename()` a simple mutation primitive; the no-op guard is a CLI-layer concern.

**Error classes on `AgentRegistry`:** Mirrors `AgentNameInUseError` / `AgentPidPollTimeoutError` pattern in `agent.service.ts`. CLI catches them for user-friendly messages.

## Non-Functional Requirements

- Atomic write: use existing `.tmp` + `renameSync` pattern. No data loss on crash mid-write.
- No new runtime dependencies.
65 changes: 65 additions & 0 deletions docs/ai/implementation/2026-05-31-feature-agent-rename.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
phase: implementation
title: Implementation Guide
description: Technical implementation notes, patterns, and code guidelines
---

# Implementation Guide

## Development Setup
**How do we get started?**

- Prerequisites and dependencies
- Environment setup steps
- Configuration needed

## Code Structure
**How is the code organized?**

- Directory structure
- Module organization
- Naming conventions

## Implementation Notes
**Key technical details to remember:**

### Core Features
- Feature 1: Implementation approach
- Feature 2: Implementation approach
- Feature 3: Implementation approach

### Patterns & Best Practices
- Design patterns being used
- Code style guidelines
- Common utilities/helpers

## Integration Points
**How do pieces connect?**

- API integration details
- Database connections
- Third-party service setup

## Error Handling
**How do we handle failures?**

- Error handling strategy
- Logging approach
- Retry/fallback mechanisms

## Performance Considerations
**How do we keep it fast?**

- Optimization strategies
- Caching approach
- Query optimization
- Resource management

## Security Notes
**What security measures are in place?**

- Authentication/authorization
- Input validation
- Data encryption
- Secrets management

28 changes: 28 additions & 0 deletions docs/ai/planning/2026-05-31-feature-agent-rename.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
phase: planning
title: Project Planning & Task Breakdown
description: Break down work into actionable tasks and estimate timeline
---

# Project Planning & Task Breakdown

## Task Breakdown

- [x] Task 1: Add `RenameNotFoundError` and `RenameConflictError` to `AgentRegistry`, implement `rename()` method, and export new error classes from `packages/agent-manager/src/index.ts`
- [x] Task 2: Add `agent rename <current-name> <new-name>` subcommand to `packages/cli/src/commands/agent.ts`
- [x] Task 3: Write unit tests for `AgentRegistry.rename()` in `packages/agent-manager/src/__tests__/utils/AgentRegistry.test.ts`
- [x] Task 4: Write unit tests for the `agent rename` CLI command in `packages/cli/src/__tests__/commands/agent.test.ts`

## Dependencies

- Task 2 depends on Task 1 (needs `rename()` and error classes from agent-manager).
- Tasks 3 and 4 can be written after their respective implementation tasks.
- No external dependencies or infrastructure changes.

## Risks & Mitigation

| Risk | Mitigation |
|------|-----------|
| `AgentRegistry` exports not updated | Verify `packages/agent-manager/src/index.ts` re-exports new symbols |
| Stale entry blocks rename when it should not | Prune before conflict check, matching `startAgent` pattern |
| CLI test for `agent rename` doesn't exist yet | Add alongside the command, following existing `agent.test.ts` patterns |
52 changes: 52 additions & 0 deletions docs/ai/requirements/2026-05-31-feature-agent-rename.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
phase: requirements
title: Requirements & Problem Understanding
description: Clarify the problem space, gather requirements, and define success criteria
---

# Requirements & Problem Understanding

## Problem Statement

Running agents accumulate auto-generated names like `my-project-1748700000` that are hard to recall. There is currently no way to assign a more meaningful name to a running agent after it has been started. Users must stop and restart the agent with a new `--name` to rename it, losing context.

Affected users: CLI users who manage multiple long-lived agents via `agent list`, `agent send`, `agent open`.

## Goals & Objectives

**Primary goal:** Allow a user to rename a registered agent by running `agent rename <current-name> <new-name>`.

**Non-goals:**
- Renaming the underlying tmux session (registry-only change, per decision)
- Renaming an agent that is not in the registry (stale/unknown agents are out of scope)
- Bulk rename or pattern-based rename

## User Stories & Use Cases

- As a CLI user, I want to run `agent rename my-project-1abc2d my-api-agent` so the agent shows a readable name in `agent list` and I can reference it easily with `agent send --id my-api-agent`.
- As a CLI user, if I supply a `<new-name>` that is already in use, I want a clear error telling me the conflict.
- As a CLI user, if I supply a `<current-name>` not in the registry, I want a clear error.
- As a CLI user, if I supply an invalid `<new-name>` format, I want a validation error with the format rules.

**Edge cases:**
- Current name and new name are the same → no-op success or informational message.
- New name exists but the entry is stale (process dead) → prune first, then allow rename.

## Success Criteria

- `agent rename <current> <new>` updates `name` in `~/.ai-devkit/agents.json` atomically.
- The renamed agent appears under the new name in `agent list`.
- `agent send --id <new-name>` and `agent open <new-name>` resolve correctly after rename.
- All error paths (not found, conflict, invalid format) produce actionable messages and exit code 1.
- Existing unit tests for `AgentRegistry` pass; new tests cover `rename()`.

## Constraints & Assumptions

- Name format: `/^[a-z0-9][a-z0-9-]{0,62}[a-z0-9]$/` (existing `NAME_REGEX` in `agent.ts`).
- Registry file: `~/.ai-devkit/agents.json` (atomic write via `.tmp` + `rename`).
- The tmux session name is NOT updated — `tmuxSession` field in the entry retains its original value.
- No distributed locking; single-writer pattern is already established.

## Questions & Open Items

All material questions resolved. No open items.
81 changes: 81 additions & 0 deletions docs/ai/testing/2026-05-31-feature-agent-rename.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
---
phase: testing
title: Testing Strategy
description: Define testing approach, test cases, and quality assurance
---

# Testing Strategy

## Test Coverage Goals
**What level of testing do we aim for?**

- Unit test coverage target (default: 100% of new/changed code)
- Integration test scope (critical paths + error handling)
- End-to-end test scenarios (key user journeys)
- Alignment with requirements/design acceptance criteria

## Unit Tests
**What individual components need testing?**

### Component/Module 1
- [ ] Test case 1: [Description] (covers scenario / branch)
- [ ] Test case 2: [Description] (covers edge case / error handling)
- [ ] Additional coverage: [Description]

### Component/Module 2
- [ ] Test case 1: [Description]
- [ ] Test case 2: [Description]
- [ ] Additional coverage: [Description]

## Integration Tests
**How do we test component interactions?**

- [ ] Integration scenario 1
- [ ] Integration scenario 2
- [ ] API endpoint tests
- [ ] Integration scenario 3 (failure mode / rollback)

## End-to-End Tests
**What user flows need validation?**

- [ ] User flow 1: [Description]
- [ ] User flow 2: [Description]
- [ ] Critical path testing
- [ ] Regression of adjacent features

## Test Data
**What data do we use for testing?**

- Test fixtures and mocks
- Seed data requirements
- Test database setup

## Test Reporting & Coverage
**How do we verify and communicate test results?**

- Coverage commands and thresholds (`npm run test -- --coverage`)
- Coverage gaps (files/functions below 100% and rationale)
- Links to test reports or dashboards
- Manual testing outcomes and sign-off

## Manual Testing
**What requires human validation?**

- UI/UX testing checklist (include accessibility)
- Browser/device compatibility
- Smoke tests after deployment

## Performance Testing
**How do we validate performance?**

- Load testing scenarios
- Stress testing approach
- Performance benchmarks

## Bug Tracking
**How do we manage issues?**

- Issue tracking process
- Bug severity levels
- Regression testing strategy

43 changes: 42 additions & 1 deletion packages/agent-manager/src/__tests__/utils/AgentRegistry.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from 'fs';
import os from 'os';
import path from 'path';
import { AgentRegistry, type RegistryEntry } from '../../utils/AgentRegistry.js';
import { AgentRegistry, RenameNotFoundError, RenameConflictError, type RegistryEntry } from '../../utils/AgentRegistry.js';

function makeEntry(over: Partial<RegistryEntry> = {}): RegistryEntry {
return {
Expand Down Expand Up @@ -179,4 +179,45 @@ describe('AgentRegistry', () => {
expect(AgentRegistry.default()).toBe(AgentRegistry.default());
});
});

describe('rename', () => {
it('updates the name of an existing entry', () => {
registry.register(makeEntry({ name: 'old-name', pid: process.pid }));
registry.rename('old-name', 'new-name');
expect(registry.lookup('new-name')?.name).toBe('new-name');
expect(registry.lookup('old-name')).toBeNull();
});

it('preserves all other fields on the renamed entry', () => {
registry.register(makeEntry({ name: 'old-name', pid: process.pid, tmuxSession: 'old-name', cwd: '/my/cwd' }));
registry.rename('old-name', 'new-name');
const entry = registry.lookup('new-name');
expect(entry?.tmuxSession).toBe('old-name');
expect(entry?.cwd).toBe('/my/cwd');
expect(entry?.pid).toBe(process.pid);
});

it('throws RenameNotFoundError when current name does not exist', () => {
expect(() => registry.rename('ghost', 'new-name')).toThrow(RenameNotFoundError);
});

it('throws RenameConflictError when new name is already in use by a live entry', () => {
registry.register(makeEntry({ name: 'agent-a', pid: process.pid }));
registry.register(makeEntry({ name: 'agent-b', pid: process.pid }));
expect(() => registry.rename('agent-a', 'agent-b')).toThrow(RenameConflictError);
});

it('succeeds when new name exists only as a stale (dead) entry', () => {
registry.register(makeEntry({ name: 'agent-a', pid: process.pid }));
registry.register(makeEntry({ name: 'agent-b', pid: 999999 }));
expect(() => registry.rename('agent-a', 'agent-b')).not.toThrow();
expect(registry.lookup('agent-b')?.pid).toBe(process.pid);
});

it('writes atomically (no leftover .tmp on success)', () => {
registry.register(makeEntry({ name: 'old-name', pid: process.pid }));
registry.rename('old-name', 'new-name');
expect(fs.existsSync(`${regPath}.tmp`)).toBe(false);
});
});
});
2 changes: 1 addition & 1 deletion packages/agent-manager/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export { getProcessTty } from './utils/process.js';
export type { AgentSortKey } from './utils/sortAgents.js';
export type { ListAgentsOptions } from './AgentManager.js';

export { AgentRegistry } from './utils/AgentRegistry.js';
export { AgentRegistry, RenameNotFoundError, RenameConflictError } from './utils/AgentRegistry.js';
export type { RegistryEntry } from './utils/AgentRegistry.js';
export { TmuxManager } from './terminal/TmuxManager.js';
export { AGENTS } from './utils/agents.js';
Expand Down
Loading
Loading