-
Notifications
You must be signed in to change notification settings - Fork 1
Open
Description
Summary
Enable "Run loop B after loop A completes" for staged workflows like implement → test → review.
Current State
Loop relationships exist for reviews:
parentLoopId- Links child to parent (src/core/types.ts:61)reviewLoopId- Links parent to review (src/core/types.ts:60)isReviewLoop- Flags review loops (src/core/types.ts:62)
Review flow: src/core/loops.ts (lines 1212-1344)
createReviewLoop()creates linked reviewcreateFollowUpFromReview()creates follow-up from review feedback
No dependency chain system exists.
Implementation
Phase 1: Extend Loop Type
File: src/core/types.ts
Add dependency fields:
export interface Loop {
// ... existing fields
// Dependency chain fields
dependsOn?: string; // ID of loop that must complete first
dependentLoops?: string[]; // IDs of loops waiting on this one
chainId?: string; // Group ID for chain visualization
autoStartOnComplete?: boolean; // Auto-start when dependency completes
}
export type DependencyStatus = 'waiting' | 'ready' | 'blocked';Phase 2: Dependency Management
File: src/core/loops.ts
Add dependency functions:
export function addDependency(
loopId: string,
dependsOnId: string,
autoStart: boolean = true
): void {
let state = loadState();
const loop = state.loops.find(l => l.id === loopId);
const dependsOn = state.loops.find(l => l.id === dependsOnId);
if (!loop || !dependsOn) throw new Error('Loop not found');
if (dependsOn.status === 'completed') throw new Error('Cannot depend on completed loop');
// Check for circular dependency
if (wouldCreateCycle(loopId, dependsOnId, state)) {
throw new Error('Circular dependency detected');
}
// Update dependent loop
state = updateLoop(state, loopId, {
dependsOn: dependsOnId,
autoStartOnComplete: autoStart,
status: 'queued', // Can't run until dependency completes
});
// Update parent's dependentLoops array
const dependents = dependsOn.dependentLoops || [];
state = updateLoop(state, dependsOnId, {
dependentLoops: [...dependents, loopId],
});
saveState(state);
emit({ type: 'dependency-added', loopId, dependsOnId });
}
export function removeDependency(loopId: string): void {
let state = loadState();
const loop = state.loops.find(l => l.id === loopId);
if (!loop?.dependsOn) return;
const parentId = loop.dependsOn;
const parent = state.loops.find(l => l.id === parentId);
// Remove from parent's dependentLoops
if (parent?.dependentLoops) {
state = updateLoop(state, parentId, {
dependentLoops: parent.dependentLoops.filter(id => id !== loopId),
});
}
// Clear dependency on loop
state = updateLoop(state, loopId, {
dependsOn: undefined,
autoStartOnComplete: undefined,
});
saveState(state);
}
function wouldCreateCycle(loopId: string, newDepId: string, state: AppState): boolean {
// BFS to detect if newDepId eventually depends on loopId
const visited = new Set<string>();
const queue = [newDepId];
while (queue.length > 0) {
const current = queue.shift()!;
if (current === loopId) return true;
if (visited.has(current)) continue;
visited.add(current);
const loop = state.loops.find(l => l.id === current);
if (loop?.dependsOn) queue.push(loop.dependsOn);
}
return false;
}
export function getDependencyStatus(loop: Loop, state: AppState): DependencyStatus {
if (!loop.dependsOn) return 'ready';
const dependency = state.loops.find(l => l.id === loop.dependsOn);
if (!dependency) return 'ready'; // Dependency deleted
if (dependency.status === 'completed') return 'ready';
if (dependency.status === 'error' || dependency.status === 'stopped') return 'blocked';
return 'waiting';
}Phase 3: Auto-Start on Completion
File: src/core/loops.ts
Update finalizeLoop() to trigger dependents:
// In finalizeLoop() after setting status to 'completed':
if (loop.dependentLoops?.length) {
for (const depId of loop.dependentLoops) {
const depLoop = state.loops.find(l => l.id === depId);
if (depLoop?.autoStartOnComplete && depLoop.status === 'queued') {
// Queue auto-start (don't start synchronously)
setTimeout(() => startLoop(depId), 100);
}
}
}Phase 4: UI - Link Dependencies
File: src/index.ts
Add keybind to link loops:
// 'D' key to add dependency
loopListWindow.key('d', () => {
if (!selectedLoopId) return;
showDependencyModal(selectedLoopId);
});
function showDependencyModal(loopId: string): void {
const loop = state.loops.find(l => l.id === loopId);
if (!loop) return;
// Show list of other loops to depend on
const candidates = state.loops.filter(l =>
l.id !== loopId &&
l.status !== 'completed' &&
l.dependsOn !== loopId // Avoid immediate cycles
);
// Modal with selectable list
// On select: addDependency(loopId, selectedDep.id)
}Phase 5: Visual Indicators
File: src/index.ts
Update loop list formatting:
function formatLoopListItem(loop: Loop): string {
// ... existing formatting
// Add dependency indicator
if (loop.dependsOn) {
const depStatus = getDependencyStatus(loop, state);
const depIcon = depStatus === 'waiting' ? '⏳' : depStatus === 'blocked' ? '🚫' : '→';
// Prepend: "⏳ " or "→ " to show dependency status
}
// Show dependent count
if (loop.dependentLoops?.length) {
// Append: " +2" to show 2 loops depend on this
}
}Update detail pane to show chain:
// In detail pane, show dependency info:
// "Depends on: #42 Implement auth (running)"
// "Dependents: #43 Add tests, #44 Review code"Files to Modify
src/core/types.ts- Add dependency fields to Loop interfacesrc/core/loops.ts- Add dependency functions, update finalizeLoop()src/index.ts- Add D keybind, dependency modal, visual indicatorssrc/ui/theme.ts- Add dependency status colors
Acceptance Criteria
- Add dependency via D key → shows modal to select parent loop
- Dependent loop shows "waiting" status until parent completes
- Auto-start dependent loop when parent completes successfully
- Visual indicator showing chain status (⏳ waiting, → ready, 🚫 blocked)
- Show "+N" badge for loops with dependents
- Cancel chain option if parent fails/errors (don't auto-start, mark blocked)
- Circular dependency detection prevents invalid chains
- Detail pane shows full dependency info
Testing
- Create loop A, create loop B, press D on B → select A
- Start loop A → B should show "waiting" status
- Complete A → B should auto-start
- Create A → B → C chain, verify cascade works
- Error A → B should show "blocked", not auto-start
- Try to create B → A when A → B exists → should prevent cycle
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels