good!
This commit is contained in:
27
README.md
27
README.md
@@ -44,10 +44,35 @@ Execute tasks from task files using DAG-based dependency resolution with persist
|
|||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
1 -> 2
|
1 -> 2,3
|
||||||
2 -> 3
|
2 -> 3
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Supported Dependency Formats
|
||||||
|
|
||||||
|
The parser supports two dependency declaration styles in the `## Dependencies` section:
|
||||||
|
|
||||||
|
**Arrow Notation** (recommended):
|
||||||
|
```
|
||||||
|
1 -> 2,3,4
|
||||||
|
5 -> 6
|
||||||
|
```
|
||||||
|
This means: "Task 1 must complete before tasks 2, 3, and 4 can start."
|
||||||
|
|
||||||
|
**Natural Language**:
|
||||||
|
```
|
||||||
|
13 depends on 17, 18, 19, 20
|
||||||
|
14 depends on 13, 15, 16
|
||||||
|
```
|
||||||
|
This means: "Task 13 depends on tasks 17, 18, 19, and 20."
|
||||||
|
|
||||||
|
**Parallel Groups** (informational only):
|
||||||
|
```
|
||||||
|
1, 2, 3, 4 can be done in parallel
|
||||||
|
5, 6, 7, 8 can be done in parallel
|
||||||
|
```
|
||||||
|
Note: These lines are ignored by the parser. Use explicit dependencies to control execution order.
|
||||||
|
|
||||||
### Simple Checkbox Format
|
### Simple Checkbox Format
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
|
|||||||
16
index.ts
16
index.ts
@@ -153,7 +153,11 @@ export default function ralphLoopExtension(pi: ExtensionAPI): void {
|
|||||||
const found = findProgressFile(process.cwd());
|
const found = findProgressFile(process.cwd());
|
||||||
if (found) {
|
if (found) {
|
||||||
ctx.ui.notify(
|
ctx.ui.notify(
|
||||||
`Unknown command: ${command}\n\nFound existing progress in ${found.path}\nUse /ralph resume to continue.\n\nAvailable: ${COMMANDS.join(", ")}`,
|
`Unknown command: ${command}\n\nFound existing progress in ${
|
||||||
|
found.path
|
||||||
|
}\nUse /ralph resume to continue.\n\nAvailable: ${COMMANDS.join(
|
||||||
|
", ",
|
||||||
|
)}`,
|
||||||
"warning",
|
"warning",
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -305,7 +309,9 @@ async function handleStatus(
|
|||||||
// No progress yet for this task — parse and show plan instead
|
// No progress yet for this task — parse and show plan instead
|
||||||
const project = parseTaskFile(taskFile);
|
const project = parseTaskFile(taskFile);
|
||||||
ctx.ui.notify(
|
ctx.ui.notify(
|
||||||
`No progress for ${path.basename(taskFile)}. ${project.tasks.length} tasks found.\nUse /ralph run ${args[0]} to start.`,
|
`No progress for ${path.basename(taskFile)}. ${
|
||||||
|
project.tasks.length
|
||||||
|
} tasks found.\nUse /ralph run ${args[0]} to start.`,
|
||||||
"info",
|
"info",
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@@ -374,7 +380,7 @@ async function handleResume(
|
|||||||
const completed = new Set(progress.getCompletedTaskIds());
|
const completed = new Set(progress.getCompletedTaskIds());
|
||||||
|
|
||||||
// Ask user for execution mode
|
// Ask user for execution mode
|
||||||
const mode = await ctx.ui.select("Execution mode for this resume?", [
|
const mode = await ctx.ui.select("Execution mode for this run?", [
|
||||||
"Parallel (DAG-optimized)",
|
"Parallel (DAG-optimized)",
|
||||||
"Sequential (one at a time)",
|
"Sequential (one at a time)",
|
||||||
]);
|
]);
|
||||||
@@ -477,7 +483,9 @@ async function handleNext(
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx.ui.notify(
|
ctx.ui.notify(
|
||||||
`Executed: ${nextBatch.map((t) => t.id).join(", ")}\n\n${formatProgressStatus(progress.getState())}`,
|
`Executed: ${nextBatch
|
||||||
|
.map((t) => t.id)
|
||||||
|
.join(", ")}\n\n${formatProgressStatus(progress.getState())}`,
|
||||||
"info",
|
"info",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,27 +71,37 @@ export async function runTask(
|
|||||||
|
|
||||||
const taskHeader = `${task.id} · ${task.title}`;
|
const taskHeader = `${task.id} · ${task.title}`;
|
||||||
|
|
||||||
// Live progress widget above the editor — animated spinner + tool call updates
|
// Live progress widget above the editor — animated spinner + tool call tree
|
||||||
// Using setWidget instead of setWorkingMessage because the working message area
|
// Using setWidget instead of setWorkingMessage because the working message area
|
||||||
// is only visible during parent agent streaming, not during extension command execution.
|
// is only visible during parent agent streaming, not during extension command execution.
|
||||||
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
||||||
let frameIndex = 0;
|
let frameIndex = 0;
|
||||||
let lastToolLabel = "";
|
|
||||||
const theme = ctx.ui.theme;
|
const theme = ctx.ui.theme;
|
||||||
|
const MAX_COLLAPSED = 3;
|
||||||
|
|
||||||
const toolCalls: ToolCallEntry[] = [];
|
const toolCalls: ToolCallEntry[] = [];
|
||||||
|
|
||||||
const updateWidget = () => {
|
const updateWidget = () => {
|
||||||
const frame = theme.fg("accent", SPINNER_FRAMES[frameIndex]);
|
const frame = theme.fg("accent", SPINNER_FRAMES[frameIndex]);
|
||||||
const lines = [`${frame} ${taskHeader}`];
|
const lines = [`${frame} ${taskHeader}`];
|
||||||
|
|
||||||
if (toolCalls.length > 0) {
|
if (toolCalls.length > 0) {
|
||||||
lines.push(
|
const shown = toolCalls.slice(-MAX_COLLAPSED);
|
||||||
theme.fg(
|
const remaining = toolCalls.length - shown.length;
|
||||||
"dim",
|
|
||||||
` ${toolCalls.length} tool${toolCalls.length !== 1 ? "s" : ""} · ${lastToolLabel}`,
|
if (remaining > 0) {
|
||||||
),
|
lines.push(theme.fg("dim", ` ├── ${remaining} more`));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < shown.length; i++) {
|
||||||
|
const entry = shown[i];
|
||||||
|
const isLast = i === shown.length - 1;
|
||||||
|
const branch = isLast ? " └── " : " ├── ";
|
||||||
|
const tag = theme.fg("accent", `[${entry.name}]`);
|
||||||
|
lines.push(`${branch}${tag} ${entry.label}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ctx.ui.setWidget("ralph-task", lines);
|
ctx.ui.setWidget("ralph-task", lines);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -119,8 +129,6 @@ export async function runTask(
|
|||||||
name: event.toolName,
|
name: event.toolName,
|
||||||
label,
|
label,
|
||||||
});
|
});
|
||||||
// Update widget with latest tool call info
|
|
||||||
lastToolLabel = `[${event.toolName}] ${label}`;
|
|
||||||
updateWidget();
|
updateWidget();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -88,13 +88,38 @@ function parseFioFormat(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (inDeps) {
|
if (inDeps) {
|
||||||
const depMatch = line.match(/^(\d+)\s*->\s*(\d+)/);
|
// Format 2: Arrow notation with multiple targets
|
||||||
if (depMatch) {
|
// "01 -> 02,03,06 (description)" means 02, 03, 06 depend on 01
|
||||||
const [, from, to] = depMatch;
|
const arrowMatch = line.match(/^(\d+)\s*->\s*([\d,\s]+?)(?:\s*\(|$)/);
|
||||||
|
if (arrowMatch) {
|
||||||
|
const [, from, targets] = arrowMatch;
|
||||||
const fromId = `0${from}`;
|
const fromId = `0${from}`;
|
||||||
const toId = `0${to}`;
|
const targetIds = targets
|
||||||
if (!dependencies[fromId]) dependencies[fromId] = [];
|
.split(",")
|
||||||
dependencies[fromId].push(toId);
|
.map((t) => t.trim())
|
||||||
|
.filter((t) => t)
|
||||||
|
.map((t) => `0${t}`);
|
||||||
|
|
||||||
|
// Each target depends on the source
|
||||||
|
for (const toId of targetIds) {
|
||||||
|
if (!dependencies[toId]) dependencies[toId] = [];
|
||||||
|
dependencies[toId].push(fromId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format 1: Natural language "X depends on A, B, C"
|
||||||
|
const dependsMatch = line.match(/^(\d+)\s+depends\s+on\s+([\d,\s]+)/i);
|
||||||
|
if (dependsMatch) {
|
||||||
|
const [, taskId, depsList] = dependsMatch;
|
||||||
|
const taskIdPadded = `0${taskId}`;
|
||||||
|
const depIds = depsList
|
||||||
|
.split(",")
|
||||||
|
.map((t) => t.trim())
|
||||||
|
.filter((t) => t)
|
||||||
|
.map((t) => `0${t}`);
|
||||||
|
|
||||||
|
if (!dependencies[taskIdPadded]) dependencies[taskIdPadded] = [];
|
||||||
|
dependencies[taskIdPadded].push(...depIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse meta blocks for task configuration (timeout, etc.)
|
// Parse meta blocks for task configuration (timeout, etc.)
|
||||||
@@ -126,6 +151,13 @@ function parseFioFormat(
|
|||||||
const objectiveMatch = content.match(/^#\s+(.+)$/m);
|
const objectiveMatch = content.match(/^#\s+(.+)$/m);
|
||||||
const objective = objectiveMatch ? objectiveMatch[1].trim() : undefined;
|
const objective = objectiveMatch ? objectiveMatch[1].trim() : undefined;
|
||||||
|
|
||||||
|
// Apply dependencies map to task.dependencies arrays
|
||||||
|
for (const task of tasks) {
|
||||||
|
if (dependencies[task.id]) {
|
||||||
|
task.dependencies = dependencies[task.id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tasks,
|
tasks,
|
||||||
dependencies,
|
dependencies,
|
||||||
|
|||||||
Reference in New Issue
Block a user