Files
Codeman/src/task.ts
T
arkonandClaude Opus 4.5 d722aca2c7 refactor: pre-compile regex patterns and remove dead code
- task.ts: Cache completion phrase regex in constructor instead of
  re-creating on every checkCompletion() call. Add escapeRegex helper
  for safe RegExp construction from user-provided phrases.
- ralph-tracker.ts: Remove commented-out COMPLETION_SIGNAL_PATTERN

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 04:36:18 +01:00

221 lines
5.7 KiB
TypeScript

/**
* @fileoverview Task model for Claude prompt execution
*
* Represents a single task (prompt) to be executed by a Claude session.
* Tasks support priority ordering, dependencies, and completion detection.
*
* @module task
*/
import { v4 as uuidv4 } from 'uuid';
import { TaskDefinition, TaskState, TaskStatus } from './types.js';
/** Pre-compiled pattern for generic promise tag detection */
const PROMISE_TAG_PATTERN = /<promise>[^<]+<\/promise>/;
/** Escapes special regex characters in a string for safe use in RegExp constructor */
function escapeRegex(str: string): string {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
/**
* Options for creating a new task.
*/
export interface CreateTaskOptions {
prompt: string;
workingDir?: string;
priority?: number;
dependencies?: string[];
completionPhrase?: string;
timeoutMs?: number;
}
/**
* A task representing a prompt to be executed by a Claude session.
*
* @description
* Tasks have a lifecycle: pending → running → completed/failed
* They support priority ordering and dependency chains.
*/
export class Task {
readonly id: string;
readonly prompt: string;
readonly workingDir: string;
readonly priority: number;
readonly dependencies: string[];
readonly completionPhrase: string | undefined;
readonly timeoutMs: number | undefined;
readonly createdAt: number;
private _status: TaskStatus = 'pending';
private _assignedSessionId: string | null = null;
private _startedAt: number | null = null;
private _completedAt: number | null = null;
private _output: string = '';
private _error: string | null = null;
/** Pre-compiled regex for completion phrase detection (avoids re-creation per check) */
private readonly _completionPattern: RegExp | null;
constructor(options: CreateTaskOptions, id?: string) {
this.id = id || uuidv4();
this.prompt = options.prompt;
this.workingDir = options.workingDir || process.cwd();
this.priority = options.priority ?? 0;
this.dependencies = options.dependencies || [];
this.completionPhrase = options.completionPhrase;
this.timeoutMs = options.timeoutMs;
this.createdAt = Date.now();
this._completionPattern = options.completionPhrase
? new RegExp(`<promise>${escapeRegex(options.completionPhrase)}</promise>`)
: null;
}
get status(): TaskStatus {
return this._status;
}
get assignedSessionId(): string | null {
return this._assignedSessionId;
}
get startedAt(): number | null {
return this._startedAt;
}
get completedAt(): number | null {
return this._completedAt;
}
get output(): string {
return this._output;
}
get error(): string | null {
return this._error;
}
isPending(): boolean {
return this._status === 'pending';
}
isRunning(): boolean {
return this._status === 'running';
}
isCompleted(): boolean {
return this._status === 'completed';
}
isFailed(): boolean {
return this._status === 'failed';
}
isDone(): boolean {
return this._status === 'completed' || this._status === 'failed';
}
toDefinition(): TaskDefinition {
return {
id: this.id,
prompt: this.prompt,
workingDir: this.workingDir,
priority: this.priority,
dependencies: this.dependencies,
completionPhrase: this.completionPhrase,
timeoutMs: this.timeoutMs,
};
}
toState(): TaskState {
return {
...this.toDefinition(),
status: this._status,
assignedSessionId: this._assignedSessionId,
createdAt: this.createdAt,
startedAt: this._startedAt,
completedAt: this._completedAt,
output: this._output,
error: this._error,
};
}
/** Reconstructs a Task from persisted state. */
static fromState(state: TaskState): Task {
const task = new Task(
{
prompt: state.prompt,
workingDir: state.workingDir,
priority: state.priority,
dependencies: state.dependencies,
completionPhrase: state.completionPhrase,
timeoutMs: state.timeoutMs,
},
state.id
);
task._status = state.status;
task._assignedSessionId = state.assignedSessionId;
task._startedAt = state.startedAt;
task._completedAt = state.completedAt;
task._output = state.output;
task._error = state.error;
return task;
}
/** Assigns this task to a session and marks it as running. */
assign(sessionId: string): void {
if (this._status !== 'pending') {
throw new Error(`Cannot assign task ${this.id}: status is ${this._status}`);
}
this._status = 'running';
this._assignedSessionId = sessionId;
this._startedAt = Date.now();
}
appendOutput(output: string): void {
this._output += output;
}
setError(error: string): void {
this._error = error;
}
complete(): void {
this._status = 'completed';
this._completedAt = Date.now();
}
fail(error?: string): void {
this._status = 'failed';
this._completedAt = Date.now();
if (error) {
this._error = error;
}
}
reset(): void {
this._status = 'pending';
this._assignedSessionId = null;
this._startedAt = null;
this._completedAt = null;
this._output = '';
this._error = null;
}
/** Checks if output contains the completion phrase. */
checkCompletion(output: string): boolean {
if (this._completionPattern) {
return this._completionPattern.test(output);
}
// Default: check for any promise tag completion
return PROMISE_TAG_PATTERN.test(output);
}
/** Returns true if the task has exceeded its timeout. */
isTimedOut(): boolean {
if (!this.timeoutMs || !this._startedAt) {
return false;
}
return Date.now() - this._startedAt > this.timeoutMs;
}
}