mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 00:19:42 +02:00
chore: project setup and tooling cleanup (#26)
* add project setup * add tools to eslint * remove contributing.md * update claude md * chore: simplify CI to single Node.js version (22) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * use node 20 * adjust formatting * fix linting * format * fix layout * fix: align CI node version to .nvmrc (22), remove redundant gotcha Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: arkon <arkon.85@hotmail.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
arkon
parent
2b2829ebde
commit
480584de63
+179
-154
@@ -34,12 +34,7 @@ import {
|
||||
PlanTaskStatus,
|
||||
TddPhase,
|
||||
} from './types.js';
|
||||
import {
|
||||
ANSI_ESCAPE_PATTERN_SIMPLE,
|
||||
fuzzyPhraseMatch,
|
||||
todoContentHash,
|
||||
stringSimilarity,
|
||||
} from './utils/index.js';
|
||||
import { ANSI_ESCAPE_PATTERN_SIMPLE, fuzzyPhraseMatch, todoContentHash, stringSimilarity } from './utils/index.js';
|
||||
import { MAX_LINE_BUFFER_SIZE } from './config/buffer-limits.js';
|
||||
import { MAX_TODOS_PER_SESSION } from './config/map-limits.js';
|
||||
|
||||
@@ -151,8 +146,19 @@ const MAX_PLAN_HISTORY = 10;
|
||||
* P1-002: Configurable false positive prevention
|
||||
*/
|
||||
const COMMON_COMPLETION_PHRASES = new Set([
|
||||
'DONE', 'COMPLETE', 'FINISHED', 'OK', 'YES', 'TRUE', 'SUCCESS',
|
||||
'READY', 'COMPLETED', 'PASSED', 'END', 'STOP', 'EXIT',
|
||||
'DONE',
|
||||
'COMPLETE',
|
||||
'FINISHED',
|
||||
'OK',
|
||||
'YES',
|
||||
'TRUE',
|
||||
'SUCCESS',
|
||||
'READY',
|
||||
'COMPLETED',
|
||||
'PASSED',
|
||||
'END',
|
||||
'STOP',
|
||||
'EXIT',
|
||||
]);
|
||||
|
||||
/**
|
||||
@@ -249,9 +255,9 @@ const TODO_PLAIN_CHECKMARK_PATTERN = /✔\s+(.+)/g;
|
||||
* Prevents false positives from tool invocations and Claude commentary
|
||||
*/
|
||||
const TODO_EXCLUDE_PATTERNS = [
|
||||
/^(?:Bash|Search|Read|Write|Glob|Grep|Edit|Task)\s*\(/i, // Tool invocations
|
||||
/^(?:I'll |Let me |Now I|First,|Task \d+:|Result:|Error:)/i, // Claude commentary
|
||||
/^\S+\([^)]+\)$/, // Generic function call pattern
|
||||
/^(?:Bash|Search|Read|Write|Glob|Grep|Edit|Task)\s*\(/i, // Tool invocations
|
||||
/^(?:I'll |Let me |Now I|First,|Task \d+:|Result:|Error:)/i, // Claude commentary
|
||||
/^\S+\([^)]+\)$/, // Generic function call pattern
|
||||
];
|
||||
|
||||
// ---------- Loop Status Patterns ----------
|
||||
@@ -313,7 +319,8 @@ const TODOWRITE_PATTERN = /TodoWrite|todo(?:s)?\s*(?:updated|written|saved)|Todo
|
||||
* Examples: "All 8 files have been created", "All tasks completed", "Everything is done"
|
||||
* Used to mark all tracked todos as complete at once
|
||||
*/
|
||||
const ALL_COMPLETE_PATTERN = /all\s+(?:\d+\s+)?(?:tasks?|files?|items?)\s+(?:have\s+been\s+|are\s+)?(?:completed?|done|finished|created)|completed?\s+all\s+(?:\d+\s+)?tasks?|all\s+done|everything\s+(?:is\s+)?(?:completed?|done)|finished\s+all\s+tasks?/i;
|
||||
const ALL_COMPLETE_PATTERN =
|
||||
/all\s+(?:\d+\s+)?(?:tasks?|files?|items?)\s+(?:have\s+been\s+|are\s+)?(?:completed?|done|finished|created)|completed?\s+all\s+(?:\d+\s+)?tasks?|all\s+done|everything\s+(?:is\s+)?(?:completed?|done)|finished\s+all\s+tasks?/i;
|
||||
|
||||
/**
|
||||
* Extracts count from "all N items" messages
|
||||
@@ -327,7 +334,8 @@ const ALL_COUNT_PATTERN = /all\s+(\d+)\s+(?:tasks?|files?|items?)/i;
|
||||
* Examples: "Task #5 is done", "marked as completed", "todo 3 finished"
|
||||
* Used to update specific todo items by number
|
||||
*/
|
||||
const TASK_DONE_PATTERN = /(?:task|item|todo)\s*(?:#?\d+|"\s*[^"]+\s*")?\s*(?:is\s+)?(?:done|completed?|finished)|(?:completed?|done|finished)\s+(?:task|item)\s*(?:#?\d+)?|marking\s+(?:.*?\s+)?(?:as\s+)?completed?|marked\s+(?:.*?\s+)?(?:as\s+)?completed?/i;
|
||||
const TASK_DONE_PATTERN =
|
||||
/(?:task|item|todo)\s*(?:#?\d+|"\s*[^"]+\s*")?\s*(?:is\s+)?(?:done|completed?|finished)|(?:completed?|done|finished)\s+(?:task|item)\s*(?:#?\d+)?|marking\s+(?:.*?\s+)?(?:as\s+)?completed?|marked\s+(?:.*?\s+)?(?:as\s+)?completed?/i;
|
||||
|
||||
// ---------- Utility Patterns ----------
|
||||
|
||||
@@ -411,48 +419,48 @@ const COMPLETION_INDICATOR_PATTERNS = [
|
||||
|
||||
/** P0 (Critical) priority patterns - highest severity issues */
|
||||
const P0_PRIORITY_PATTERNS = [
|
||||
/\bP0\b|\(P0\)|:?\s*P0\s*:/, // Explicit P0
|
||||
/\bCRITICAL\b/, // Critical keyword
|
||||
/\bBLOCKER\b/, // Blocker
|
||||
/\bURGENT\b/, // Urgent
|
||||
/\bSECURITY\b/, // Security issues
|
||||
/\bCRASH(?:ES|ING)?\b/, // Crash, crashes, crashing
|
||||
/\bBROKEN\b/, // Broken
|
||||
/\bDATA\s*LOSS\b/, // Data loss
|
||||
/\bPRODUCTION\s*(?:DOWN|ISSUE|BUG)\b/, // Production issues
|
||||
/\bHOTFIX\b/, // Hotfix
|
||||
/\bSEVERITY\s*1\b/, // Severity 1
|
||||
/\bP0\b|\(P0\)|:?\s*P0\s*:/, // Explicit P0
|
||||
/\bCRITICAL\b/, // Critical keyword
|
||||
/\bBLOCKER\b/, // Blocker
|
||||
/\bURGENT\b/, // Urgent
|
||||
/\bSECURITY\b/, // Security issues
|
||||
/\bCRASH(?:ES|ING)?\b/, // Crash, crashes, crashing
|
||||
/\bBROKEN\b/, // Broken
|
||||
/\bDATA\s*LOSS\b/, // Data loss
|
||||
/\bPRODUCTION\s*(?:DOWN|ISSUE|BUG)\b/, // Production issues
|
||||
/\bHOTFIX\b/, // Hotfix
|
||||
/\bSEVERITY\s*1\b/, // Severity 1
|
||||
];
|
||||
|
||||
/** P1 (High) priority patterns - important issues requiring attention */
|
||||
const P1_PRIORITY_PATTERNS = [
|
||||
/\bP1\b|\(P1\)|:?\s*P1\s*:/, // Explicit P1
|
||||
/\bHIGH\s*PRIORITY\b/, // High priority
|
||||
/\bIMPORTANT\b/, // Important
|
||||
/\bBUG\b/, // Bug
|
||||
/\bFIX\b/, // Fix (as task type)
|
||||
/\bERROR\b/, // Error
|
||||
/\bFAIL(?:S|ED|ING|URE)?\b/, // Fail variants
|
||||
/\bREGRESSION\b/, // Regression
|
||||
/\bMUST\s*(?:HAVE|FIX|DO)\b/, // Must have/fix/do
|
||||
/\bSEVERITY\s*2\b/, // Severity 2
|
||||
/\bREQUIRED\b/, // Required
|
||||
/\bP1\b|\(P1\)|:?\s*P1\s*:/, // Explicit P1
|
||||
/\bHIGH\s*PRIORITY\b/, // High priority
|
||||
/\bIMPORTANT\b/, // Important
|
||||
/\bBUG\b/, // Bug
|
||||
/\bFIX\b/, // Fix (as task type)
|
||||
/\bERROR\b/, // Error
|
||||
/\bFAIL(?:S|ED|ING|URE)?\b/, // Fail variants
|
||||
/\bREGRESSION\b/, // Regression
|
||||
/\bMUST\s*(?:HAVE|FIX|DO)\b/, // Must have/fix/do
|
||||
/\bSEVERITY\s*2\b/, // Severity 2
|
||||
/\bREQUIRED\b/, // Required
|
||||
];
|
||||
|
||||
/** P2 (Medium) priority patterns - lower priority improvements */
|
||||
const P2_PRIORITY_PATTERNS = [
|
||||
/\bP2\b|\(P2\)|:?\s*P2\s*:/, // Explicit P2
|
||||
/\bNICE\s*TO\s*HAVE\b/, // Nice to have
|
||||
/\bLOW\s*PRIORITY\b/, // Low priority
|
||||
/\bREFACTOR\b/, // Refactor
|
||||
/\bCLEANUP\b/, // Cleanup
|
||||
/\bIMPROVE(?:MENT)?\b/, // Improve/Improvement
|
||||
/\bOPTIMIZ(?:E|ATION)\b/, // Optimize/Optimization
|
||||
/\bCONSIDER\b/, // Consider
|
||||
/\bWOULD\s*BE\s*NICE\b/, // Would be nice
|
||||
/\bENHANCE(?:MENT)?\b/, // Enhance/Enhancement
|
||||
/\bTECH(?:NICAL)?\s*DEBT\b/, // Tech debt
|
||||
/\bDOCUMENT(?:ATION)?\b/, // Documentation
|
||||
/\bP2\b|\(P2\)|:?\s*P2\s*:/, // Explicit P2
|
||||
/\bNICE\s*TO\s*HAVE\b/, // Nice to have
|
||||
/\bLOW\s*PRIORITY\b/, // Low priority
|
||||
/\bREFACTOR\b/, // Refactor
|
||||
/\bCLEANUP\b/, // Cleanup
|
||||
/\bIMPROVE(?:MENT)?\b/, // Improve/Improvement
|
||||
/\bOPTIMIZ(?:E|ATION)\b/, // Optimize/Optimization
|
||||
/\bCONSIDER\b/, // Consider
|
||||
/\bWOULD\s*BE\s*NICE\b/, // Would be nice
|
||||
/\bENHANCE(?:MENT)?\b/, // Enhance/Enhancement
|
||||
/\bTECH(?:NICAL)?\s*DEBT\b/, // Tech debt
|
||||
/\bDOCUMENT(?:ATION)?\b/, // Documentation
|
||||
];
|
||||
|
||||
// ========== Event Types ==========
|
||||
@@ -950,7 +958,7 @@ export class RalphTracker extends EventEmitter {
|
||||
|
||||
const wasEnabled = this._loopState.enabled;
|
||||
this._loopState = createInitialRalphTrackerState();
|
||||
this._loopState.enabled = wasEnabled; // Keep enabled status
|
||||
this._loopState.enabled = wasEnabled; // Keep enabled status
|
||||
this._todos.clear();
|
||||
this._completionPhraseCount.clear();
|
||||
this._taskNumberToContent.clear();
|
||||
@@ -1241,7 +1249,7 @@ export class RalphTracker extends EventEmitter {
|
||||
|
||||
// Check if all todos are complete (adds 20 points)
|
||||
const todoArray = Array.from(this._todos.values());
|
||||
if (todoArray.length > 0 && todoArray.every(t => t.status === 'completed')) {
|
||||
if (todoArray.length > 0 && todoArray.every((t) => t.status === 'completed')) {
|
||||
signals.allTodosComplete = true;
|
||||
score += 20;
|
||||
}
|
||||
@@ -1262,10 +1270,12 @@ export class RalphTracker extends EventEmitter {
|
||||
if (context) {
|
||||
const lowerContext = context.toLowerCase();
|
||||
// Deduct points if phrase appears in prompt-like context
|
||||
if (lowerContext.includes('output:') ||
|
||||
lowerContext.includes('completion phrase') ||
|
||||
lowerContext.includes('output exactly') ||
|
||||
lowerContext.includes('when done')) {
|
||||
if (
|
||||
lowerContext.includes('output:') ||
|
||||
lowerContext.includes('completion phrase') ||
|
||||
lowerContext.includes('output exactly') ||
|
||||
lowerContext.includes('when done')
|
||||
) {
|
||||
signals.contextAppropriate = false;
|
||||
score -= 20;
|
||||
} else {
|
||||
@@ -1335,7 +1345,6 @@ export class RalphTracker extends EventEmitter {
|
||||
* Use this when the caller has already stripped ANSI to avoid redundant regex work.
|
||||
*/
|
||||
processCleanData(cleanData: string): void {
|
||||
|
||||
// If tracker is disabled, only check for patterns that should auto-enable it
|
||||
if (!this._loopState.enabled) {
|
||||
// Don't auto-enable if explicitly disabled by user setting
|
||||
@@ -1396,14 +1405,21 @@ export class RalphTracker extends EventEmitter {
|
||||
// substrings that any pattern could match are present in the data.
|
||||
// This avoids 12 regex tests on every PTY chunk (the common case).
|
||||
if (
|
||||
!data.includes('<') && // <promise>, TodoWrite
|
||||
!data.includes('ralph') && !data.includes('Ralph') &&
|
||||
!data.includes('Todo') && !data.includes('todo') &&
|
||||
!data.includes('Iteration') && !data.includes('[') &&
|
||||
!data.includes('\u2610') && !data.includes('\u2612') && // ☐ ☒
|
||||
!data.includes('<') && // <promise>, TodoWrite
|
||||
!data.includes('ralph') &&
|
||||
!data.includes('Ralph') &&
|
||||
!data.includes('Todo') &&
|
||||
!data.includes('todo') &&
|
||||
!data.includes('Iteration') &&
|
||||
!data.includes('[') &&
|
||||
!data.includes('\u2610') &&
|
||||
!data.includes('\u2612') && // ☐ ☒
|
||||
!data.includes('\u2714') && // ✔
|
||||
!data.includes('Loop') && !data.includes('complete') &&
|
||||
!data.includes('COMPLETE') && !data.includes('Done') && !data.includes('DONE')
|
||||
!data.includes('Loop') &&
|
||||
!data.includes('complete') &&
|
||||
!data.includes('COMPLETE') &&
|
||||
!data.includes('Done') &&
|
||||
!data.includes('DONE')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@@ -1677,8 +1693,8 @@ export class RalphTracker extends EventEmitter {
|
||||
// Avoid false positives: don't trigger on prompt context
|
||||
const isNotInPromptContext = !line.includes('<promise>') && !line.includes('output:');
|
||||
// Also avoid triggering on "completion phrase is X" explanatory text
|
||||
const isNotExplanation = !line.toLowerCase().includes('completion phrase') &&
|
||||
!line.toLowerCase().includes('output exactly');
|
||||
const isNotExplanation =
|
||||
!line.toLowerCase().includes('completion phrase') && !line.toLowerCase().includes('output exactly');
|
||||
|
||||
if (isNotInPromptContext && isNotExplanation) {
|
||||
this.handleBareCompletionPhrase(expectedPhrase);
|
||||
@@ -1786,7 +1802,7 @@ export class RalphTracker extends EventEmitter {
|
||||
|
||||
if (matchedPhrase) {
|
||||
// Use the matched phrase (canonical) for tracking
|
||||
const canonicalCount = (this._completionPhraseCount.get(matchedPhrase) || 0);
|
||||
const canonicalCount = this._completionPhraseCount.get(matchedPhrase) || 0;
|
||||
// Require 2nd+ occurrence of canonical phrase OR explicitly active loop.
|
||||
// First occurrence (count=1) is the prompt echo — not actual completion.
|
||||
if (canonicalCount >= 2 || this._loopState.active) {
|
||||
@@ -1862,7 +1878,7 @@ export class RalphTracker extends EventEmitter {
|
||||
* @fires phraseValidationWarning - When a risky phrase is detected
|
||||
*/
|
||||
private validateCompletionPhrase(phrase: string): void {
|
||||
const normalized = phrase.toUpperCase().replace(/[\s_\-\.]+/g, '');
|
||||
const normalized = phrase.toUpperCase().replace(/[\s_\-.]+/g, '');
|
||||
|
||||
// Generate a suggested unique phrase
|
||||
const uniqueSuffix = Date.now().toString(36).slice(-4).toUpperCase();
|
||||
@@ -1870,7 +1886,9 @@ export class RalphTracker extends EventEmitter {
|
||||
|
||||
// Check for common phrases
|
||||
if (COMMON_COMPLETION_PHRASES.has(normalized)) {
|
||||
console.warn(`[RalphTracker] Warning: Completion phrase "${phrase}" is very common and may cause false positives. Consider using: "${suggestedPhrase}"`);
|
||||
console.warn(
|
||||
`[RalphTracker] Warning: Completion phrase "${phrase}" is very common and may cause false positives. Consider using: "${suggestedPhrase}"`
|
||||
);
|
||||
this.emit('phraseValidationWarning', {
|
||||
phrase,
|
||||
reason: 'common',
|
||||
@@ -1881,7 +1899,9 @@ export class RalphTracker extends EventEmitter {
|
||||
|
||||
// Check for short phrases
|
||||
if (normalized.length < MIN_RECOMMENDED_PHRASE_LENGTH) {
|
||||
console.warn(`[RalphTracker] Warning: Completion phrase "${phrase}" is too short (${normalized.length} chars). Consider using: "${suggestedPhrase}"`);
|
||||
console.warn(
|
||||
`[RalphTracker] Warning: Completion phrase "${phrase}" is too short (${normalized.length} chars). Consider using: "${suggestedPhrase}"`
|
||||
);
|
||||
this.emit('phraseValidationWarning', {
|
||||
phrase,
|
||||
reason: 'short',
|
||||
@@ -1892,7 +1912,9 @@ export class RalphTracker extends EventEmitter {
|
||||
|
||||
// Check for numeric-only phrases
|
||||
if (/^\d+$/.test(normalized)) {
|
||||
console.warn(`[RalphTracker] Warning: Completion phrase "${phrase}" is numeric-only and may cause false positives. Consider using: "${suggestedPhrase}"`);
|
||||
console.warn(
|
||||
`[RalphTracker] Warning: Completion phrase "${phrase}" is numeric-only and may cause false positives. Consider using: "${suggestedPhrase}"`
|
||||
);
|
||||
this.emit('phraseValidationWarning', {
|
||||
phrase,
|
||||
reason: 'numeric',
|
||||
@@ -1971,14 +1993,16 @@ export class RalphTracker extends EventEmitter {
|
||||
if (currentIter !== this._lastObservedIteration) {
|
||||
this._lastIterationChangeTime = Date.now();
|
||||
this._lastObservedIteration = currentIter;
|
||||
this._iterationStallWarned = false; // Reset warning on iteration change
|
||||
this._iterationStallWarned = false; // Reset warning on iteration change
|
||||
|
||||
// P1-004: Reset circuit breaker on successful iteration progress
|
||||
// If we're making progress, the loop is healthy
|
||||
if (this._circuitBreaker.state === 'HALF_OPEN' ||
|
||||
this._circuitBreaker.consecutiveNoProgress > 0 ||
|
||||
this._circuitBreaker.consecutiveSameError > 0 ||
|
||||
this._circuitBreaker.consecutiveTestsFailure > 0) {
|
||||
if (
|
||||
this._circuitBreaker.state === 'HALF_OPEN' ||
|
||||
this._circuitBreaker.consecutiveNoProgress > 0 ||
|
||||
this._circuitBreaker.consecutiveSameError > 0 ||
|
||||
this._circuitBreaker.consecutiveTestsFailure > 0
|
||||
) {
|
||||
this._circuitBreaker.consecutiveNoProgress = 0;
|
||||
this._circuitBreaker.consecutiveSameError = 0;
|
||||
this._circuitBreaker.lastProgressIteration = currentIter;
|
||||
@@ -2106,7 +2130,7 @@ export class RalphTracker extends EventEmitter {
|
||||
const content = match[2].trim();
|
||||
|
||||
// Skip if content matches exclude patterns (tool invocations, commentary)
|
||||
const shouldExclude = TODO_EXCLUDE_PATTERNS.some(pattern => pattern.test(content));
|
||||
const shouldExclude = TODO_EXCLUDE_PATTERNS.some((pattern) => pattern.test(content));
|
||||
if (shouldExclude) continue;
|
||||
|
||||
// Skip if content is too short or looks like partial garbage
|
||||
@@ -2155,9 +2179,8 @@ export class RalphTracker extends EventEmitter {
|
||||
while ((match = TODO_TASK_STATUS_PATTERN.exec(line)) !== null) {
|
||||
const taskNum = parseInt(match[1], 10);
|
||||
const statusStr = match[2].trim();
|
||||
const status: RalphTodoStatus = statusStr === 'completed' ? 'completed'
|
||||
: statusStr === 'in progress' ? 'in_progress'
|
||||
: 'pending';
|
||||
const status: RalphTodoStatus =
|
||||
statusStr === 'completed' ? 'completed' : statusStr === 'in progress' ? 'in_progress' : 'pending';
|
||||
const content = this._taskNumberToContent.get(taskNum);
|
||||
if (content) {
|
||||
this.upsertTodo(content, status);
|
||||
@@ -2172,7 +2195,7 @@ export class RalphTracker extends EventEmitter {
|
||||
while ((match = TODO_PLAIN_CHECKMARK_PATTERN.exec(line)) !== null) {
|
||||
const content = match[1].trim();
|
||||
// Skip if content matches exclude patterns
|
||||
const shouldExclude = TODO_EXCLUDE_PATTERNS.some(pattern => pattern.test(content));
|
||||
const shouldExclude = TODO_EXCLUDE_PATTERNS.some((pattern) => pattern.test(content));
|
||||
if (shouldExclude) continue;
|
||||
if (content.length < 5) continue;
|
||||
// Skip status/created/updated prefixed content (already handled above)
|
||||
@@ -2204,17 +2227,17 @@ export class RalphTracker extends EventEmitter {
|
||||
switch (icon) {
|
||||
case '✓':
|
||||
case '✅':
|
||||
case '☒': // Claude Code checked checkbox
|
||||
case '◉': // Filled circle (completed)
|
||||
case '●': // Solid circle (completed)
|
||||
case '☒': // Claude Code checked checkbox
|
||||
case '◉': // Filled circle (completed)
|
||||
case '●': // Solid circle (completed)
|
||||
return 'completed';
|
||||
case '◐': // Half-filled circle (in progress)
|
||||
case '◐': // Half-filled circle (in progress)
|
||||
case '⏳':
|
||||
case '⌛':
|
||||
case '🔄':
|
||||
return 'in_progress';
|
||||
case '☐': // Claude Code empty checkbox
|
||||
case '○': // Empty circle
|
||||
case '☐': // Claude Code empty checkbox
|
||||
case '○': // Empty circle
|
||||
default:
|
||||
return 'pending';
|
||||
}
|
||||
@@ -2280,10 +2303,10 @@ export class RalphTracker extends EventEmitter {
|
||||
|
||||
// Clean content: remove ANSI codes, collapse whitespace, trim
|
||||
const cleanContent = content
|
||||
.replace(ANSI_ESCAPE_PATTERN_SIMPLE, '') // Remove ANSI escape codes
|
||||
.replace(/\s+/g, ' ') // Collapse whitespace
|
||||
.replace(ANSI_ESCAPE_PATTERN_SIMPLE, '') // Remove ANSI escape codes
|
||||
.replace(/\s+/g, ' ') // Collapse whitespace
|
||||
.trim();
|
||||
if (cleanContent.length < 5) return; // Skip very short content
|
||||
if (cleanContent.length < 5) return; // Skip very short content
|
||||
|
||||
// Parse priority from content
|
||||
const priority = this.parsePriority(cleanContent);
|
||||
@@ -2402,8 +2425,8 @@ export class RalphTracker extends EventEmitter {
|
||||
private normalizeTodoContent(content: string): string {
|
||||
if (!content) return '';
|
||||
return content
|
||||
.replace(/\s+/g, ' ') // Collapse whitespace
|
||||
.replace(/[^a-zA-Z0-9\s.,!?'"-]/g, '') // Remove special chars (keep punctuation)
|
||||
.replace(/\s+/g, ' ') // Collapse whitespace
|
||||
.replace(/[^a-zA-Z0-9\s.,!?'"-]/g, '') // Remove special chars (keep punctuation)
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
}
|
||||
@@ -2507,7 +2530,7 @@ export class RalphTracker extends EventEmitter {
|
||||
if (normalized.length < 30) {
|
||||
threshold = 0.95; // Very strict for short strings
|
||||
} else if (normalized.length < 60) {
|
||||
threshold = 0.90; // Strict for medium strings
|
||||
threshold = 0.9; // Strict for medium strings
|
||||
} else {
|
||||
threshold = TODO_SIMILARITY_THRESHOLD; // 0.85 for longer strings
|
||||
}
|
||||
@@ -2563,14 +2586,7 @@ export class RalphTracker extends EventEmitter {
|
||||
];
|
||||
|
||||
// Moderate: Bugs, features, enhancements
|
||||
const moderatePatterns = [
|
||||
/\bbug\b/,
|
||||
/\bfeature\b/,
|
||||
/\benhance(?:ment)?\b/,
|
||||
/\bimplement\b/,
|
||||
/\badd\b/,
|
||||
/\bfix\b/,
|
||||
];
|
||||
const moderatePatterns = [/\bbug\b/, /\bfeature\b/, /\benhance(?:ment)?\b/, /\bimplement\b/, /\badd\b/, /\bfix\b/];
|
||||
|
||||
for (const pattern of complexPatterns) {
|
||||
if (pattern.test(lower)) return 'complex';
|
||||
@@ -2609,10 +2625,10 @@ export class RalphTracker extends EventEmitter {
|
||||
|
||||
// Default estimates (in ms) based on typical task durations
|
||||
const defaults = {
|
||||
trivial: 1 * 60 * 1000, // 1 minute
|
||||
simple: 3 * 60 * 1000, // 3 minutes
|
||||
moderate: 10 * 60 * 1000, // 10 minutes
|
||||
complex: 30 * 60 * 1000, // 30 minutes
|
||||
trivial: 1 * 60 * 1000, // 1 minute
|
||||
simple: 3 * 60 * 1000, // 3 minutes
|
||||
moderate: 10 * 60 * 1000, // 10 minutes
|
||||
complex: 30 * 60 * 1000, // 30 minutes
|
||||
};
|
||||
return defaults[complexity];
|
||||
}
|
||||
@@ -2671,9 +2687,9 @@ export class RalphTracker extends EventEmitter {
|
||||
public getTodoProgress(): RalphTodoProgress {
|
||||
const todos = Array.from(this._todos.values());
|
||||
const total = todos.length;
|
||||
const completed = todos.filter(t => t.status === 'completed').length;
|
||||
const inProgress = todos.filter(t => t.status === 'in_progress').length;
|
||||
const pending = todos.filter(t => t.status === 'pending').length;
|
||||
const completed = todos.filter((t) => t.status === 'completed').length;
|
||||
const inProgress = todos.filter((t) => t.status === 'in_progress').length;
|
||||
const pending = todos.filter((t) => t.status === 'pending').length;
|
||||
|
||||
const percentComplete = total > 0 ? Math.round((completed / total) * 100) : 0;
|
||||
|
||||
@@ -2983,7 +2999,7 @@ export class RalphTracker extends EventEmitter {
|
||||
// Ensure enabled flag exists (backwards compatibility)
|
||||
this._loopState = {
|
||||
...loopState,
|
||||
enabled: loopState.enabled ?? false, // Override after spread for backwards compat
|
||||
enabled: loopState.enabled ?? false, // Override after spread for backwards compat
|
||||
};
|
||||
this._todos.clear();
|
||||
for (const todo of todos) {
|
||||
@@ -3068,7 +3084,9 @@ export class RalphTracker extends EventEmitter {
|
||||
if (!Number.isNaN(value) && value >= 0) {
|
||||
block.tasksCompletedThisLoop = value;
|
||||
} else {
|
||||
parseErrors.push(`Invalid TASKS_COMPLETED_THIS_LOOP value: "${tasksMatch[1]}". Expected: non-negative integer`);
|
||||
parseErrors.push(
|
||||
`Invalid TASKS_COMPLETED_THIS_LOOP value: "${tasksMatch[1]}". Expected: non-negative integer`
|
||||
);
|
||||
}
|
||||
matched = true;
|
||||
}
|
||||
@@ -3104,7 +3122,9 @@ export class RalphTracker extends EventEmitter {
|
||||
if (['IMPLEMENTATION', 'TESTING', 'DOCUMENTATION', 'REFACTORING'].includes(value)) {
|
||||
block.workType = value as RalphWorkType;
|
||||
} else {
|
||||
parseErrors.push(`Invalid WORK_TYPE value: "${value}". Expected: IMPLEMENTATION, TESTING, DOCUMENTATION, or REFACTORING`);
|
||||
parseErrors.push(
|
||||
`Invalid WORK_TYPE value: "${value}". Expected: IMPLEMENTATION, TESTING, DOCUMENTATION, or REFACTORING`
|
||||
);
|
||||
}
|
||||
matched = true;
|
||||
}
|
||||
@@ -3126,7 +3146,7 @@ export class RalphTracker extends EventEmitter {
|
||||
// Track unknown fields for debugging (only if looks like a field)
|
||||
if (!matched && trimmedLine.includes(':')) {
|
||||
const fieldName = trimmedLine.split(':')[0].trim().toUpperCase();
|
||||
if (fieldName && !['#', '//'].some(c => fieldName.startsWith(c))) {
|
||||
if (fieldName && !['#', '//'].some((c) => fieldName.startsWith(c))) {
|
||||
unknownFields.push(fieldName);
|
||||
}
|
||||
}
|
||||
@@ -3221,11 +3241,7 @@ export class RalphTracker extends EventEmitter {
|
||||
* @param status - Overall status from RALPH_STATUS
|
||||
* @fires circuitBreakerUpdate - If state changes
|
||||
*/
|
||||
private updateCircuitBreaker(
|
||||
hasProgress: boolean,
|
||||
testsStatus: RalphTestsStatus,
|
||||
status: RalphStatusValue
|
||||
): void {
|
||||
private updateCircuitBreaker(hasProgress: boolean, testsStatus: RalphTestsStatus, status: RalphStatusValue): void {
|
||||
const prevState = this._circuitBreaker.state;
|
||||
|
||||
if (hasProgress) {
|
||||
@@ -3319,7 +3335,11 @@ export class RalphTracker extends EventEmitter {
|
||||
/**
|
||||
* Get cumulative stats from status blocks.
|
||||
*/
|
||||
get cumulativeStats(): { filesModified: number; tasksCompleted: number; completionIndicators: number } {
|
||||
get cumulativeStats(): {
|
||||
filesModified: number;
|
||||
tasksCompleted: number;
|
||||
completionIndicators: number;
|
||||
} {
|
||||
return {
|
||||
filesModified: this._totalFilesModified,
|
||||
tasksCompleted: this._totalTasksCompleted,
|
||||
@@ -3456,7 +3476,7 @@ export class RalphTracker extends EventEmitter {
|
||||
const tasksHeaderPattern = /^##\s*Tasks/i;
|
||||
|
||||
// Pattern for todo items
|
||||
const todoPattern = /^-\s*\[([ x\-])\]\s*(.+)$/;
|
||||
const todoPattern = /^-\s*\[([ x-])\]\s*(.+)$/;
|
||||
|
||||
let inCompletedSection = false;
|
||||
|
||||
@@ -3504,7 +3524,7 @@ export class RalphTracker extends EventEmitter {
|
||||
}
|
||||
|
||||
// Parse priority from content if not in a priority section
|
||||
const parsedPriority = inCompletedSection ? null : (currentPriority || this.parsePriority(content));
|
||||
const parsedPriority = inCompletedSection ? null : currentPriority || this.parsePriority(content);
|
||||
|
||||
const id = this.generateTodoId(content);
|
||||
newTodos.push({
|
||||
@@ -3535,17 +3555,19 @@ export class RalphTracker extends EventEmitter {
|
||||
* 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 {
|
||||
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');
|
||||
@@ -3580,11 +3602,14 @@ export class RalphTracker extends EventEmitter {
|
||||
/**
|
||||
* 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 } {
|
||||
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' };
|
||||
@@ -3635,7 +3660,7 @@ export class RalphTracker extends EventEmitter {
|
||||
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 allDepsComplete = task.dependencies.every((depId) => {
|
||||
const dep = this._planTasks.get(depId);
|
||||
return dep && dep.status === 'completed';
|
||||
});
|
||||
@@ -3653,8 +3678,7 @@ export class RalphTracker extends EventEmitter {
|
||||
*/
|
||||
private _checkForCheckpoint(): void {
|
||||
const currentIteration = this._loopState.cycleCount;
|
||||
if (this._checkpointIterations.includes(currentIteration) &&
|
||||
currentIteration > this._lastCheckpointIteration) {
|
||||
if (this._checkpointIterations.includes(currentIteration) && currentIteration > this._lastCheckpointIteration) {
|
||||
this._lastCheckpointIteration = currentIteration;
|
||||
const checkpoint = this.generateCheckpointReview();
|
||||
this.emit('planCheckpoint', checkpoint);
|
||||
@@ -3669,17 +3693,17 @@ export class RalphTracker extends EventEmitter {
|
||||
|
||||
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,
|
||||
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 => ({
|
||||
.filter((t) => t.attempts >= 3 || t.status === 'blocked')
|
||||
.map((t) => ({
|
||||
id: t.id,
|
||||
content: t.content,
|
||||
attempts: t.attempts,
|
||||
@@ -3697,9 +3721,7 @@ export class RalphTracker extends EventEmitter {
|
||||
recommendations.push('More tasks have failed than completed. Review approach and consider plan adjustment.');
|
||||
}
|
||||
|
||||
const progressPercent = summary.total > 0
|
||||
? Math.round((summary.completed / summary.total) * 100)
|
||||
: 0;
|
||||
const progressPercent = summary.total > 0 ? Math.round((summary.completed / summary.total) * 100) : 0;
|
||||
if (progressPercent < 20 && this._loopState.cycleCount > 10) {
|
||||
recommendations.push('Progress is slow. Consider simplifying tasks or reviewing dependencies.');
|
||||
}
|
||||
@@ -3749,7 +3771,7 @@ export class RalphTracker extends EventEmitter {
|
||||
summary: string;
|
||||
stats: { total: number; completed: number; failed: number };
|
||||
}> {
|
||||
return this._planHistory.map(h => {
|
||||
return this._planHistory.map((h) => {
|
||||
const tasks = Array.from(h.tasks.values());
|
||||
return {
|
||||
version: h.version,
|
||||
@@ -3757,8 +3779,8 @@ export class RalphTracker extends EventEmitter {
|
||||
summary: h.summary,
|
||||
stats: {
|
||||
total: tasks.length,
|
||||
completed: tasks.filter(t => t.status === 'completed').length,
|
||||
failed: tasks.filter(t => t.status === 'failed').length,
|
||||
completed: tasks.filter((t) => t.status === 'completed').length,
|
||||
failed: tasks.filter((t) => t.status === 'failed').length,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -3767,8 +3789,12 @@ export class RalphTracker extends EventEmitter {
|
||||
/**
|
||||
* Rollback to a previous plan version.
|
||||
*/
|
||||
rollbackToVersion(version: number): { success: boolean; plan?: EnhancedPlanTask[]; error?: string } {
|
||||
const historyEntry = this._planHistory.find(h => h.version === 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` };
|
||||
}
|
||||
@@ -3807,7 +3833,7 @@ export class RalphTracker extends EventEmitter {
|
||||
// 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 counter = existingIds.filter((id) => id.startsWith(prefix)).length + 1;
|
||||
let id = `${prefix}-${String(counter).padStart(3, '0')}`;
|
||||
while (this._planTasks.has(id)) {
|
||||
counter++;
|
||||
@@ -3850,8 +3876,7 @@ export class RalphTracker extends EventEmitter {
|
||||
*/
|
||||
isCheckpointDue(): boolean {
|
||||
const currentIteration = this._loopState.cycleCount;
|
||||
return this._checkpointIterations.includes(currentIteration) &&
|
||||
currentIteration > this._lastCheckpointIteration;
|
||||
return this._checkpointIterations.includes(currentIteration) && currentIteration > this._lastCheckpointIteration;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user