mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-09 16:59:43 +02:00
feat: disable Ralph/Todo tracker auto-enable by default
Ralph tracker no longer auto-enables on pattern detection. It must be explicitly enabled per-session (via API) or globally via the new `ralphEnabled` AppConfig setting. Adds GET/PUT /api/config endpoints for runtime configuration. Spawn agent ralph enable is unchanged. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -310,7 +310,7 @@ export class RalphTracker extends EventEmitter {
|
|||||||
private _loopUpdatePending: boolean = false;
|
private _loopUpdatePending: boolean = false;
|
||||||
|
|
||||||
/** When true, prevents auto-enable on pattern detection */
|
/** When true, prevents auto-enable on pattern detection */
|
||||||
private _autoEnableDisabled: boolean = false;
|
private _autoEnableDisabled: boolean = true;
|
||||||
|
|
||||||
/** Maps task numbers from "✔ Task #N" format to their content for status updates */
|
/** Maps task numbers from "✔ Task #N" format to their content for status updates */
|
||||||
private _taskNumberToContent: Map<number, string> = new Map();
|
private _taskNumberToContent: Map<number, string> = new Map();
|
||||||
|
|||||||
@@ -229,6 +229,8 @@ export interface AppConfig {
|
|||||||
respawn: RespawnConfig;
|
respawn: RespawnConfig;
|
||||||
/** Last used case name (for default selection) */
|
/** Last used case name (for default selection) */
|
||||||
lastUsedCase: string | null;
|
lastUsedCase: string | null;
|
||||||
|
/** Whether Ralph/Todo tracker is globally enabled for all new sessions */
|
||||||
|
ralphEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Output Types ==========
|
// ========== Output Types ==========
|
||||||
@@ -564,6 +566,7 @@ export const DEFAULT_CONFIG: AppConfig = {
|
|||||||
sendInit: true, // send /init after /clear
|
sendInit: true, // send /init after /clear
|
||||||
},
|
},
|
||||||
lastUsedCase: null,
|
lastUsedCase: null,
|
||||||
|
ralphEnabled: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== Inner Loop Tracking Types ==========
|
// ========== Inner Loop Tracking Types ==========
|
||||||
|
|||||||
+30
-4
@@ -213,6 +213,19 @@ export class WebServer extends EventEmitter {
|
|||||||
// API Routes
|
// API Routes
|
||||||
this.app.get('/api/status', async () => this.getFullState());
|
this.app.get('/api/status', async () => this.getFullState());
|
||||||
|
|
||||||
|
this.app.get('/api/config', async () => {
|
||||||
|
return { success: true, config: this.store.getConfig() };
|
||||||
|
});
|
||||||
|
|
||||||
|
this.app.put('/api/config', async (req) => {
|
||||||
|
const body = req.body as Record<string, unknown> | null;
|
||||||
|
if (!body || typeof body !== 'object') {
|
||||||
|
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Request body must be a JSON object');
|
||||||
|
}
|
||||||
|
this.store.setConfig(body as Partial<ReturnType<typeof this.store.getConfig>>);
|
||||||
|
return { success: true, config: this.store.getConfig() };
|
||||||
|
});
|
||||||
|
|
||||||
// Session management
|
// Session management
|
||||||
this.app.get('/api/sessions', async () => this.getSessionsState());
|
this.app.get('/api/sessions', async () => this.getSessionsState());
|
||||||
|
|
||||||
@@ -449,8 +462,13 @@ export class WebServer extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Auto-detect completion phrase from CLAUDE.md BEFORE starting
|
// Auto-detect completion phrase from CLAUDE.md BEFORE starting (only if globally enabled)
|
||||||
|
if (this.store.getConfig().ralphEnabled) {
|
||||||
autoConfigureRalph(session, session.workingDir, () => {});
|
autoConfigureRalph(session, session.workingDir, () => {});
|
||||||
|
if (!session.ralphTracker.enabled) {
|
||||||
|
session.ralphTracker.enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await session.startInteractive();
|
await session.startInteractive();
|
||||||
this.broadcast('session:interactive', { id });
|
this.broadcast('session:interactive', { id });
|
||||||
@@ -723,8 +741,13 @@ export class WebServer extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Auto-detect completion phrase from CLAUDE.md BEFORE starting
|
// Auto-detect completion phrase from CLAUDE.md BEFORE starting (only if globally enabled)
|
||||||
|
if (this.store.getConfig().ralphEnabled) {
|
||||||
autoConfigureRalph(session, session.workingDir, () => {});
|
autoConfigureRalph(session, session.workingDir, () => {});
|
||||||
|
if (!session.ralphTracker.enabled) {
|
||||||
|
session.ralphTracker.enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Start interactive session
|
// Start interactive session
|
||||||
await session.startInteractive();
|
await session.startInteractive();
|
||||||
@@ -1165,9 +1188,12 @@ export class WebServer extends EventEmitter {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Auto-detect completion phrase from CLAUDE.md BEFORE broadcasting
|
// Auto-detect completion phrase from CLAUDE.md BEFORE broadcasting
|
||||||
// so the initial state already has the phrase configured
|
// so the initial state already has the phrase configured (only if globally enabled)
|
||||||
if (mode === 'claude') {
|
if (mode === 'claude' && this.store.getConfig().ralphEnabled) {
|
||||||
autoConfigureRalph(session, casePath, () => {}); // no broadcast yet
|
autoConfigureRalph(session, casePath, () => {}); // no broadcast yet
|
||||||
|
if (!session.ralphTracker.enabled) {
|
||||||
|
session.ralphTracker.enable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sessions.set(session.id, session);
|
this.sessions.set(session.id, session);
|
||||||
|
|||||||
+44
-26
@@ -48,10 +48,40 @@ describe('RalphTracker', () => {
|
|||||||
expect(freshTracker.loopState.elapsedHours).toBeNull();
|
expect(freshTracker.loopState.elapsedHours).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should auto-enable on /ralph-loop:ralph-loop command', () => {
|
it('should not auto-enable by default (auto-enable disabled)', () => {
|
||||||
|
const freshTracker = new RalphTracker();
|
||||||
|
freshTracker.processTerminalData('/ralph-loop:ralph-loop\n');
|
||||||
|
|
||||||
|
expect(freshTracker.enabled).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not auto-enable on completion phrase by default', () => {
|
||||||
|
const freshTracker = new RalphTracker();
|
||||||
|
freshTracker.processTerminalData('<promise>COMPLETE</promise>\n');
|
||||||
|
|
||||||
|
expect(freshTracker.enabled).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not auto-enable on TodoWrite by default', () => {
|
||||||
|
const freshTracker = new RalphTracker();
|
||||||
|
freshTracker.processTerminalData('TodoWrite: Todos have been modified\n');
|
||||||
|
|
||||||
|
expect(freshTracker.enabled).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not auto-enable on todo checkboxes by default', () => {
|
||||||
|
const freshTracker = new RalphTracker();
|
||||||
|
freshTracker.processTerminalData('- [ ] New task\n');
|
||||||
|
|
||||||
|
expect(freshTracker.enabled).toBe(false);
|
||||||
|
expect(freshTracker.todos).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should auto-enable when enableAutoEnable() is called', () => {
|
||||||
const freshTracker = new RalphTracker();
|
const freshTracker = new RalphTracker();
|
||||||
const enableHandler = vi.fn();
|
const enableHandler = vi.fn();
|
||||||
freshTracker.on('enabled', enableHandler);
|
freshTracker.on('enabled', enableHandler);
|
||||||
|
freshTracker.enableAutoEnable();
|
||||||
|
|
||||||
freshTracker.processTerminalData('/ralph-loop:ralph-loop\n');
|
freshTracker.processTerminalData('/ralph-loop:ralph-loop\n');
|
||||||
|
|
||||||
@@ -59,37 +89,17 @@ describe('RalphTracker', () => {
|
|||||||
expect(enableHandler).toHaveBeenCalled();
|
expect(enableHandler).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should auto-enable on completion phrase', () => {
|
it('should auto-enable on iteration patterns when auto-enable allowed', () => {
|
||||||
const freshTracker = new RalphTracker();
|
|
||||||
freshTracker.processTerminalData('<promise>COMPLETE</promise>\n');
|
|
||||||
|
|
||||||
expect(freshTracker.enabled).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should auto-enable on TodoWrite detection', () => {
|
|
||||||
const freshTracker = new RalphTracker();
|
|
||||||
freshTracker.processTerminalData('TodoWrite: Todos have been modified\n');
|
|
||||||
|
|
||||||
expect(freshTracker.enabled).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should auto-enable on todo checkboxes', () => {
|
|
||||||
const freshTracker = new RalphTracker();
|
|
||||||
freshTracker.processTerminalData('- [ ] New task\n');
|
|
||||||
|
|
||||||
expect(freshTracker.enabled).toBe(true);
|
|
||||||
expect(freshTracker.todos).toHaveLength(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should auto-enable on iteration patterns', () => {
|
|
||||||
const freshTracker = new RalphTracker();
|
const freshTracker = new RalphTracker();
|
||||||
|
freshTracker.enableAutoEnable();
|
||||||
freshTracker.processTerminalData('Iteration 5/50\n');
|
freshTracker.processTerminalData('Iteration 5/50\n');
|
||||||
|
|
||||||
expect(freshTracker.enabled).toBe(true);
|
expect(freshTracker.enabled).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should auto-enable on loop start patterns', () => {
|
it('should auto-enable on loop start patterns when auto-enable allowed', () => {
|
||||||
const freshTracker = new RalphTracker();
|
const freshTracker = new RalphTracker();
|
||||||
|
freshTracker.enableAutoEnable();
|
||||||
freshTracker.processTerminalData('Loop started at 2024-01-15\n');
|
freshTracker.processTerminalData('Loop started at 2024-01-15\n');
|
||||||
|
|
||||||
expect(freshTracker.enabled).toBe(true);
|
expect(freshTracker.enabled).toBe(true);
|
||||||
@@ -378,10 +388,18 @@ describe('RalphTracker', () => {
|
|||||||
expect(todos.filter(t => t.status === 'completed')).toHaveLength(1);
|
expect(todos.filter(t => t.status === 'completed')).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should auto-enable on native todo pattern', () => {
|
it('should not auto-enable on native todo pattern by default', () => {
|
||||||
const freshTracker = new RalphTracker();
|
const freshTracker = new RalphTracker();
|
||||||
freshTracker.processTerminalData('☐ New task\n');
|
freshTracker.processTerminalData('☐ New task\n');
|
||||||
|
|
||||||
|
expect(freshTracker.enabled).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should auto-enable on native todo pattern when auto-enable allowed', () => {
|
||||||
|
const freshTracker = new RalphTracker();
|
||||||
|
freshTracker.enableAutoEnable();
|
||||||
|
freshTracker.processTerminalData('☐ New task\n');
|
||||||
|
|
||||||
expect(freshTracker.enabled).toBe(true);
|
expect(freshTracker.enabled).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user