chore: bump version to 0.1402

This commit is contained in:
arkon
2026-01-27 23:15:25 +01:00
parent 9fc3d2eda3
commit 46434ee6d0
11 changed files with 2253 additions and 870 deletions
+416 -1
View File
@@ -31,6 +31,72 @@ import {
createInitialCircuitBreakerStatus,
} from './types.js';
// ========== Enhanced Plan Task Interface ==========
/** Task execution status for plan tracking */
export type PlanTaskStatus = 'pending' | 'in_progress' | 'completed' | 'failed' | 'blocked';
/** TDD phase categories */
export type TddPhase = 'setup' | 'test' | 'impl' | 'verify' | 'review';
/**
* Enhanced plan task with verification criteria, dependencies, and execution tracking.
* Supports TDD workflow, failure tracking, and plan versioning.
*/
export interface EnhancedPlanTask {
/** Unique identifier (e.g., "P0-001") */
id: string;
/** Task description */
content: string;
/** Criticality level */
priority: 'P0' | 'P1' | 'P2' | null;
/** How to verify completion */
verificationCriteria?: string;
/** Command to run for verification */
testCommand?: string;
/** IDs of tasks that must complete first */
dependencies: string[];
/** Current execution status */
status: PlanTaskStatus;
/** How many times attempted */
attempts: number;
/** Most recent failure reason */
lastError?: string;
/** Timestamp of completion */
completedAt?: number;
/** Plan version this belongs to */
version: number;
/** TDD phase category */
tddPhase?: TddPhase;
/** ID of paired test/impl task */
pairedWith?: string;
/** Estimated complexity */
complexity?: 'low' | 'medium' | 'high';
/** Checklist items for review tasks (tddPhase: 'review') */
reviewChecklist?: string[];
}
/** Checkpoint review data */
export interface CheckpointReview {
iteration: number;
timestamp: number;
summary: {
total: number;
completed: number;
failed: number;
blocked: number;
pending: number;
inProgress: number;
};
stuckTasks: Array<{
id: string;
content: string;
attempts: number;
lastError?: string;
}>;
recommendations: string[];
}
// ========== Configuration Constants ==========
/**
@@ -444,6 +510,28 @@ export class RalphTracker extends EventEmitter {
/** Path to the @fix_plan.md file being watched */
private _fixPlanPath: string | null = null;
// ========== Enhanced Plan Management ==========
/** Current version of the plan (incremented on changes) */
private _planVersion: number = 1;
/** History of plan versions for rollback support */
private _planHistory: Array<{
version: number;
timestamp: number;
tasks: Map<string, EnhancedPlanTask>;
summary: string;
}> = [];
/** Enhanced plan tasks with execution tracking */
private _planTasks: Map<string, EnhancedPlanTask> = new Map();
/** Checkpoint intervals (iterations at which to trigger review) */
private _checkpointIterations: number[] = [5, 10, 20, 30, 50, 75, 100];
/** Last checkpoint iteration */
private _lastCheckpointIteration: number = 0;
/**
* Creates a new RalphTracker instance.
* Starts in disabled state until Ralph patterns are detected.
@@ -778,7 +866,11 @@ export class RalphTracker extends EventEmitter {
* @returns Shallow copy of loop state (safe to modify)
*/
get loopState(): RalphTrackerState {
return { ...this._loopState };
return {
...this._loopState,
planVersion: this._planVersion,
planHistoryLength: this._planHistory.length,
};
}
/**
@@ -2338,4 +2430,327 @@ export class RalphTracker extends EventEmitter {
return newTodos.length;
}
// ========== Enhanced Plan Management Methods ==========
/**
* Initialize plan tasks from generated plan items.
* Called when wizard generates a new plan.
*/
initializePlanTasks(items: Array<{
id?: string;
content: string;
priority?: 'P0' | 'P1' | 'P2' | null;
verificationCriteria?: string;
testCommand?: string;
dependencies?: string[];
tddPhase?: TddPhase;
pairedWith?: string;
complexity?: 'low' | 'medium' | 'high';
}>): void {
// Save current plan to history before replacing
if (this._planTasks.size > 0) {
this._savePlanToHistory('Plan replaced with new generation');
}
// Clear and rebuild
this._planTasks.clear();
this._planVersion++;
items.forEach((item, idx) => {
const id = item.id || `task-${idx}`;
const task: EnhancedPlanTask = {
id,
content: item.content,
priority: item.priority || null,
verificationCriteria: item.verificationCriteria,
testCommand: item.testCommand,
dependencies: item.dependencies || [],
status: 'pending',
attempts: 0,
version: this._planVersion,
tddPhase: item.tddPhase,
pairedWith: item.pairedWith,
complexity: item.complexity,
};
this._planTasks.set(id, task);
});
this.emit('planInitialized', { version: this._planVersion, taskCount: this._planTasks.size });
}
/**
* Update a specific plan task's status, attempts, or error.
*/
updatePlanTask(taskId: string, update: {
status?: PlanTaskStatus;
error?: string;
incrementAttempts?: boolean;
}): { success: boolean; task?: EnhancedPlanTask; error?: string } {
const task = this._planTasks.get(taskId);
if (!task) {
return { success: false, error: 'Task not found' };
}
if (update.status) {
task.status = update.status;
if (update.status === 'completed') {
task.completedAt = Date.now();
}
}
if (update.error) {
task.lastError = update.error;
}
if (update.incrementAttempts) {
task.attempts++;
// After 3 failed attempts, mark as blocked and emit warning
if (task.attempts >= 3 && task.status === 'failed') {
task.status = 'blocked';
this.emit('taskBlocked', {
taskId,
content: task.content,
attempts: task.attempts,
lastError: task.lastError,
});
}
}
// Update blocked tasks when a dependency completes
if (update.status === 'completed') {
this._unblockDependentTasks(taskId);
}
// Check for checkpoint
this._checkForCheckpoint();
this.emit('planTaskUpdate', { taskId, task });
return { success: true, task };
}
/**
* Unblock tasks that were waiting on a completed dependency.
*/
private _unblockDependentTasks(completedTaskId: string): void {
for (const [_, task] of this._planTasks) {
if (task.dependencies.includes(completedTaskId)) {
// Check if all dependencies are now complete
const allDepsComplete = task.dependencies.every(depId => {
const dep = this._planTasks.get(depId);
return dep && dep.status === 'completed';
});
if (allDepsComplete && task.status === 'blocked') {
task.status = 'pending';
this.emit('taskUnblocked', { taskId: task.id });
}
}
}
}
/**
* Check if current iteration is a checkpoint and emit review if so.
*/
private _checkForCheckpoint(): void {
const currentIteration = this._loopState.cycleCount;
if (this._checkpointIterations.includes(currentIteration) &&
currentIteration > this._lastCheckpointIteration) {
this._lastCheckpointIteration = currentIteration;
const checkpoint = this.generateCheckpointReview();
this.emit('planCheckpoint', checkpoint);
}
}
/**
* Generate a checkpoint review summarizing plan progress and stuck tasks.
*/
generateCheckpointReview(): CheckpointReview {
const tasks = Array.from(this._planTasks.values());
const summary = {
total: tasks.length,
completed: tasks.filter(t => t.status === 'completed').length,
failed: tasks.filter(t => t.status === 'failed').length,
blocked: tasks.filter(t => t.status === 'blocked').length,
pending: tasks.filter(t => t.status === 'pending').length,
inProgress: tasks.filter(t => t.status === 'in_progress').length,
};
// Find stuck tasks (3+ attempts or blocked)
const stuckTasks = tasks
.filter(t => t.attempts >= 3 || t.status === 'blocked')
.map(t => ({
id: t.id,
content: t.content,
attempts: t.attempts,
lastError: t.lastError,
}));
// Generate recommendations
const recommendations: string[] = [];
if (stuckTasks.length > 0) {
recommendations.push(`${stuckTasks.length} task(s) are stuck. Consider breaking them into smaller steps.`);
}
if (summary.failed > summary.completed && summary.total > 5) {
recommendations.push('More tasks have failed than completed. Review approach and consider plan adjustment.');
}
const progressPercent = Math.round((summary.completed / summary.total) * 100);
if (progressPercent < 20 && this._loopState.cycleCount > 10) {
recommendations.push('Progress is slow. Consider simplifying tasks or reviewing dependencies.');
}
if (summary.blocked > summary.total / 3) {
recommendations.push('Many tasks are blocked. Review dependency chain for bottlenecks.');
}
return {
iteration: this._loopState.cycleCount,
timestamp: Date.now(),
summary,
stuckTasks,
recommendations,
};
}
/**
* Save current plan state to history.
*/
private _savePlanToHistory(summary: string): void {
// Clone current tasks
const tasksCopy = new Map<string, EnhancedPlanTask>();
for (const [id, task] of this._planTasks) {
tasksCopy.set(id, { ...task });
}
this._planHistory.push({
version: this._planVersion,
timestamp: Date.now(),
tasks: tasksCopy,
summary,
});
// Limit history size
if (this._planHistory.length > 10) {
this._planHistory.shift();
}
}
/**
* Get plan version history.
*/
getPlanHistory(): Array<{
version: number;
timestamp: number;
summary: string;
stats: { total: number; completed: number; failed: number };
}> {
return this._planHistory.map(h => {
const tasks = Array.from(h.tasks.values());
return {
version: h.version,
timestamp: h.timestamp,
summary: h.summary,
stats: {
total: tasks.length,
completed: tasks.filter(t => t.status === 'completed').length,
failed: tasks.filter(t => t.status === 'failed').length,
},
};
});
}
/**
* Rollback to a previous plan version.
*/
rollbackToVersion(version: number): { success: boolean; plan?: EnhancedPlanTask[]; error?: string } {
const historyEntry = this._planHistory.find(h => h.version === version);
if (!historyEntry) {
return { success: false, error: `Version ${version} not found in history` };
}
// Save current state first
this._savePlanToHistory(`Rolled back from v${this._planVersion} to v${version}`);
// Restore the historical version
this._planTasks.clear();
for (const [id, task] of historyEntry.tasks) {
// Reset execution state for retry
this._planTasks.set(id, {
...task,
status: task.status === 'completed' ? 'completed' : 'pending',
attempts: task.status === 'completed' ? task.attempts : 0,
lastError: undefined,
});
}
this._planVersion++;
this.emit('planRollback', { version, newVersion: this._planVersion });
return { success: true, plan: Array.from(this._planTasks.values()) };
}
/**
* Add a new task to the plan (for runtime adaptation).
*/
addPlanTask(task: {
content: string;
priority?: 'P0' | 'P1' | 'P2';
verificationCriteria?: string;
dependencies?: string[];
insertAfter?: string;
}): { task: EnhancedPlanTask } {
// Generate unique ID
const existingIds = Array.from(this._planTasks.keys());
const prefix = task.priority || 'P1';
let counter = existingIds.filter(id => id.startsWith(prefix)).length + 1;
let id = `${prefix}-${String(counter).padStart(3, '0')}`;
while (this._planTasks.has(id)) {
counter++;
id = `${prefix}-${String(counter).padStart(3, '0')}`;
}
const newTask: EnhancedPlanTask = {
id,
content: task.content,
priority: task.priority || null,
verificationCriteria: task.verificationCriteria || 'Task completed successfully',
dependencies: task.dependencies || [],
status: 'pending',
attempts: 0,
version: this._planVersion,
};
this._planTasks.set(id, newTask);
this.emit('planTaskAdded', { task: newTask });
return { task: newTask };
}
/**
* Get all plan tasks.
*/
getPlanTasks(): EnhancedPlanTask[] {
return Array.from(this._planTasks.values());
}
/**
* Get current plan version.
*/
get planVersion(): number {
return this._planVersion;
}
/**
* Check if checkpoint review is due for current iteration.
*/
isCheckpointDue(): boolean {
const currentIteration = this._loopState.cycleCount;
return this._checkpointIterations.includes(currentIteration) &&
currentIteration > this._lastCheckpointIteration;
}
}