From harness-claude
Encapsulates operations as command objects with undo/redo support and command queuing. Provides a Command interface and CommandHistory class for building action history systems, task queues, or job schedulers.
How this skill is triggered — by the user, by Claude, or both
Slash command
/harness-claude:gof-command-patternThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Encapsulate operations as command objects to support undo, redo, and command queuing.
Encapsulate operations as command objects to support undo, redo, and command queuing.
Command interface with undo:
interface Command {
execute(): Promise<void>;
undo(): Promise<void>;
readonly description: string;
}
// Concrete command
class CreateUserCommand implements Command {
readonly description: string;
private createdUserId: string | null = null;
constructor(
private readonly userRepo: UserRepository,
private readonly data: { email: string; name: string }
) {
this.description = `Create user: ${data.email}`;
}
async execute(): Promise<void> {
const user = await this.userRepo.create(this.data);
this.createdUserId = user.id;
console.log(`Created user ${user.id}`);
}
async undo(): Promise<void> {
if (!this.createdUserId) throw new Error('Cannot undo: command was not executed');
await this.userRepo.delete(this.createdUserId);
console.log(`Deleted user ${this.createdUserId}`);
this.createdUserId = null;
}
}
class UpdateEmailCommand implements Command {
readonly description: string;
private previousEmail: string | null = null;
constructor(
private readonly userRepo: UserRepository,
private readonly userId: string,
private readonly newEmail: string
) {
this.description = `Update email for user ${userId}`;
}
async execute(): Promise<void> {
const user = await this.userRepo.findById(this.userId);
if (!user) throw new Error(`User ${this.userId} not found`);
this.previousEmail = user.email;
await this.userRepo.update(this.userId, { email: this.newEmail });
}
async undo(): Promise<void> {
if (!this.previousEmail) throw new Error('Cannot undo: command was not executed');
await this.userRepo.update(this.userId, { email: this.previousEmail });
}
}
Command history (invoker with undo/redo stack):
class CommandHistory {
private undoStack: Command[] = [];
private redoStack: Command[] = [];
async execute(command: Command): Promise<void> {
await command.execute();
this.undoStack.push(command);
this.redoStack = []; // clear redo history after new command
console.log(`Executed: ${command.description}`);
}
async undo(): Promise<void> {
const command = this.undoStack.pop();
if (!command) throw new Error('Nothing to undo');
await command.undo();
this.redoStack.push(command);
console.log(`Undone: ${command.description}`);
}
async redo(): Promise<void> {
const command = this.redoStack.pop();
if (!command) throw new Error('Nothing to redo');
await command.execute();
this.undoStack.push(command);
console.log(`Redone: ${command.description}`);
}
getHistory(): string[] {
return this.undoStack.map((c) => c.description);
}
}
// Usage
const history = new CommandHistory();
await history.execute(new CreateUserCommand(repo, { email: '[email protected]', name: 'Alice' }));
await history.execute(new UpdateEmailCommand(repo, 'user-1', '[email protected]'));
await history.undo(); // reverts email update
await history.redo(); // re-applies email update
Command queue for background jobs:
class CommandQueue {
private queue: Command[] = [];
private running = false;
enqueue(command: Command): void {
this.queue.push(command);
if (!this.running) this.processNext();
}
private async processNext(): Promise<void> {
this.running = true;
while (this.queue.length > 0) {
const command = this.queue.shift()!;
try {
await command.execute();
} catch (err) {
console.error(`Command failed: ${command.description}`, err);
}
}
this.running = false;
}
}
Macro command (batch):
class MacroCommand implements Command {
constructor(
private readonly commands: Command[],
public readonly description: string
) {}
async execute(): Promise<void> {
for (const cmd of this.commands) await cmd.execute();
}
async undo(): Promise<void> {
for (const cmd of [...this.commands].reverse()) await cmd.undo();
}
}
When to store state for undo: The command must capture enough state before execute() to reverse it. Common patterns: capture the previous value, capture an ID for delete operations, or save a full snapshot.
Anti-patterns:
Command vs. Strategy: Strategy encapsulates an interchangeable algorithm selected at construction time. Command encapsulates a specific operation with data, executed and potentially reversed. Commands are objects that DO something once; strategies are policies applied repeatedly.
Event sourcing connection: Command pattern at the infrastructure level becomes event sourcing. Each executed command emits an event stored in an append-only log. Undo becomes a compensating event.
refactoring.guru/design-patterns/command
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeEncapsulates requests as objects for queueing, logging, undo/redo, and transactional behavior. Use when direct method calls cannot be queued, logged, or undone.
Implements the Command pattern to encapsulate operations as objects for undo, queue, and logging functionality in JavaScript.
Generates CQRS command records, handlers, validators, and request DTOs for .NET applications following Clean Architecture patterns.