reference updates
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
node_modules
|
||||
dist
|
||||
.pi-lens
|
||||
package-lock.json
|
||||
|
||||
22
AGENTS.md
22
AGENTS.md
@@ -2,7 +2,7 @@
|
||||
|
||||
## What this is
|
||||
|
||||
A Pi coding agent extension that registers the `/ralph` slash command. Not a standalone app — it runs inside Pi's extension host.
|
||||
A Pi coding agent extension that registers the `/ralpi` slash command. Not a standalone app — it runs inside Pi's extension host.
|
||||
|
||||
## Build
|
||||
|
||||
@@ -32,22 +32,22 @@ The only real npm dependency is `yaml` (^2.4.0).
|
||||
- `parser.ts` — task file parsing (Fio, checkbox, YAML formats)
|
||||
- `dag.ts` — Kahn's algorithm dependency resolution, batch planning
|
||||
- `executor.ts` — task execution, retry, parallel/sequential modes
|
||||
- `progress.ts` — `.ralph/progress.json` state management
|
||||
- `progress.ts` — `.ralpi/progress.json` state management
|
||||
- `prompts.ts` — prompt generation for spawned agent sessions
|
||||
- `reflection.ts` — reflection extraction from agent output
|
||||
- `utils.ts` — config loading, progress discovery, `runAgentSession()`
|
||||
- `types.ts` — all interfaces and `DEFAULT_CONFIG`
|
||||
- `widget-batcher.ts` — debounced widget updates for parallel tasks
|
||||
- `skills/ralph-task/SKILL.md` — Pi skill definition for task execution
|
||||
- `tasks/` — example ralph task files (self-modification history)
|
||||
- `skills/ralpi-use.md` — Pi skill definition for task execution
|
||||
- `tasks/` — example ralpi task files (self-modification history)
|
||||
|
||||
## Runtime state
|
||||
|
||||
All runtime state lives in `.ralph/` (gitignored):
|
||||
- `.ralph/progress.json` — execution progress, supports multiple PRDs
|
||||
- `.ralph/reflections/` — per-task reflection JSON files
|
||||
- `.ralph/prompts/` — generated prompts (timestamped, for debugging)
|
||||
- `.ralph/sessions/` — full session transcripts
|
||||
All runtime state lives in `.ralpi/` (gitignored):
|
||||
- `.ralpi/progress.json` — execution progress, supports multiple PRDs
|
||||
- `.ralpi/reflections/` — per-task reflection JSON files
|
||||
- `.ralpi/prompts/` — generated prompts (timestamped, for debugging)
|
||||
- `.ralpi/sessions/` — full session transcripts
|
||||
|
||||
## Task ID convention
|
||||
|
||||
@@ -55,8 +55,8 @@ Task IDs are zero-padded strings (`"01"`, `"02"`, etc.). The parser prepends `0`
|
||||
|
||||
## Command routing
|
||||
|
||||
`/ralph` with no args → plan. First token looks like a path (`@path`, `./path`, `.md`, etc.) → run. Otherwise dispatches to subcommand (`run`, `plan`, `status`, `resume`, `next`, `reset`).
|
||||
`/ralpi` with no args → plan. First token looks like a path (`@path`, `./path`, `.md`, etc.) → run. Otherwise dispatches to subcommand (`run`, `plan`, `status`, `resume`, `next`, `reset`).
|
||||
|
||||
## Config
|
||||
|
||||
Read from `.ralph/config.yaml` in project directory. Falls back to `DEFAULT_CONFIG` in `src/types.ts` when file is missing. Config is loaded at `projectDir` level, not extension level.
|
||||
Read from `.ralpi/config.yaml` in project directory. Falls back to `DEFAULT_CONFIG` in `src/types.ts` when file is missing. Config is loaded at `projectDir` level, not extension level.
|
||||
|
||||
24
README.md
24
README.md
@@ -6,7 +6,7 @@ Execute tasks from task files using DAG-based dependency resolution with persist
|
||||
|
||||
- **DAG-based execution**: Tasks are ordered by dependencies using Kahn's algorithm
|
||||
- **Parallel batching**: Independent tasks in each batch can run concurrently
|
||||
- **Persistent progress**: Execution state saved to `.ralph/progress.json`
|
||||
- **Persistent progress**: Execution state saved to `.ralpi/progress.json`
|
||||
- **Reflection system**: Each task produces a reflection for downstream tasks
|
||||
- **Retry with backoff**: Failed tasks retry with exponential backoff
|
||||
- **Multiple formats**: Supports Fio README, simple checkboxes, and YAML
|
||||
@@ -21,12 +21,12 @@ Execute tasks from task files using DAG-based dependency resolution with persist
|
||||
## Usage
|
||||
|
||||
```
|
||||
/ralph plan [task-file] # Show execution plan
|
||||
/ralph run [task-file] # Execute all tasks
|
||||
/ralph status [task-file] # Show current progress
|
||||
/ralph resume [task-file] # Resume paused execution
|
||||
/ralph next [task-file] # Execute next batch only
|
||||
/ralph reset [task-file] # Reset all progress
|
||||
/ralpi plan [task-file] # Show execution plan
|
||||
/ralpi run [task-file] # Execute all tasks
|
||||
/ralpi status [task-file] # Show current progress
|
||||
/ralpi resume [task-file] # Resume paused execution
|
||||
/ralpi next [task-file] # Execute next batch only
|
||||
/ralpi reset [task-file] # Reset all progress
|
||||
```
|
||||
|
||||
## Task File Formats
|
||||
@@ -98,7 +98,7 @@ tasks:
|
||||
|
||||
## Configuration
|
||||
|
||||
Create `.ralph/config.yaml`:
|
||||
Create `.ralpi/config.yaml`:
|
||||
|
||||
```yaml
|
||||
maxRetries: 3
|
||||
@@ -121,7 +121,7 @@ Supported formats: `10m` (minutes), `600s` (seconds), `3600000` (milliseconds)
|
||||
|
||||
## State Files
|
||||
|
||||
- `.ralph/progress.json` - Execution progress
|
||||
- `.ralph/reflections/` - Per-task reflections
|
||||
- `.ralph/prompts/` - Generated prompts
|
||||
- `.ralph/sessions/` - Full task output for review
|
||||
- `.ralpi/progress.json` - Execution progress
|
||||
- `.ralpi/reflections/` - Per-task reflections
|
||||
- `.ralpi/prompts/` - Generated prompts
|
||||
- `.ralpi/sessions/` - Full task output for review
|
||||
|
||||
52
index.ts
52
index.ts
@@ -117,7 +117,7 @@ async function executePlanBatches(
|
||||
plan: ReturnType<typeof buildPlanByMode>,
|
||||
project: Parameters<typeof buildExecutionPlan>[0],
|
||||
taskFile: string,
|
||||
config: import("./src/types").RalphConfig,
|
||||
config: import("./src/types").ralpiConfig,
|
||||
progress: ProgressTracker,
|
||||
ctx: ExtensionContext,
|
||||
mode: ExecutionMode,
|
||||
@@ -127,7 +127,7 @@ async function executePlanBatches(
|
||||
for (const batch of plan.batches) {
|
||||
if (progress.getState().paused) {
|
||||
ctx.ui.notify(
|
||||
"Execution paused. Use /ralph resume to continue.",
|
||||
"Execution paused. Use /ralpi resume to continue.",
|
||||
"warning",
|
||||
);
|
||||
return;
|
||||
@@ -135,7 +135,9 @@ async function executePlanBatches(
|
||||
|
||||
if (!Array.isArray(batch.tasks)) {
|
||||
throw new Error(
|
||||
`Batch ${batch.batchIndex} has invalid tasks: expected array, got ${typeof batch.tasks}`,
|
||||
`Batch ${
|
||||
batch.batchIndex
|
||||
} has invalid tasks: expected array, got ${typeof batch.tasks}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -159,12 +161,12 @@ async function executePlanBatches(
|
||||
|
||||
// ─── Extension Entry ────────────────────────────────────────────────────────
|
||||
|
||||
export default function ralphLoopExtension(pi: ExtensionAPI): void {
|
||||
// Register custom message renderer for ralph progress messages.
|
||||
export default function ralpiLoopExtension(pi: ExtensionAPI): void {
|
||||
// Register custom message renderer for ralpi progress messages.
|
||||
// Renders an expandable tool-call tree: collapsed shows last 3 + "N more",
|
||||
// expanded (Ctrl+O) shows every tool call.
|
||||
pi.registerMessageRenderer(
|
||||
"ralph-progress",
|
||||
"ralpi-progress",
|
||||
(message, { expanded }, theme) => {
|
||||
const details = message.details as
|
||||
| {
|
||||
@@ -218,14 +220,14 @@ export default function ralphLoopExtension(pi: ExtensionAPI): void {
|
||||
},
|
||||
);
|
||||
|
||||
pi.registerCommand("ralph", {
|
||||
pi.registerCommand("ralpi", {
|
||||
description:
|
||||
"Execute tasks from a task file using DAG-based dependency resolution",
|
||||
handler: async (args: string, ctx: ExtensionContext) => {
|
||||
const parts = (args || "").trim().split(/\s+/).filter(Boolean);
|
||||
|
||||
// Wraps pi.sendMessage() for posting status to the chat history.
|
||||
// Uses "ralph-progress" customType with a "progress" phase so the
|
||||
// Uses "ralpi-progress" customType with a "progress" phase so the
|
||||
// renderer omits the label prefix entirely (no [INFO] etc.).
|
||||
// Accepts an optional meta object with toolCalls for the expandable view.
|
||||
const sendProgress: SendChatMessage = (
|
||||
@@ -233,7 +235,7 @@ export default function ralphLoopExtension(pi: ExtensionAPI): void {
|
||||
meta?: { toolCalls?: Array<{ name: string; label: string }> },
|
||||
) => {
|
||||
pi.sendMessage({
|
||||
customType: "ralph-progress",
|
||||
customType: "ralpi-progress",
|
||||
content,
|
||||
display: true,
|
||||
details: { phase: "progress", toolCalls: meta?.toolCalls },
|
||||
@@ -270,7 +272,7 @@ export default function ralphLoopExtension(pi: ExtensionAPI): void {
|
||||
ctx.ui.notify(
|
||||
`Unknown command: ${command}\n\nFound existing progress in ${
|
||||
found.path
|
||||
}\nUse /ralph resume to continue.\n\nAvailable: ${COMMANDS.join(
|
||||
}\nUse /ralpi resume to continue.\n\nAvailable: ${COMMANDS.join(
|
||||
", ",
|
||||
)}`,
|
||||
"warning",
|
||||
@@ -287,7 +289,7 @@ export default function ralphLoopExtension(pi: ExtensionAPI): void {
|
||||
});
|
||||
}
|
||||
|
||||
// ─── /ralph plan ─────────────────────────────────────────────────────────────
|
||||
// ─── /ralpi plan ─────────────────────────────────────────────────────────────
|
||||
|
||||
async function handlePlan(
|
||||
ctx: ExtensionContext,
|
||||
@@ -308,7 +310,7 @@ async function handlePlan(
|
||||
ctx.ui.notify(`${planPrompt}\n\n${formatted}`, "info");
|
||||
}
|
||||
|
||||
// ─── /ralph run ──────────────────────────────────────────────────────────────
|
||||
// ─── /ralpi run ──────────────────────────────────────────────────────────────
|
||||
|
||||
async function handleRun(
|
||||
ctx: ExtensionContext,
|
||||
@@ -329,7 +331,7 @@ async function handleRun(
|
||||
if (found && !args[0]) {
|
||||
// Offer to resume instead of starting fresh
|
||||
const shouldResume = await ctx.ui.select(
|
||||
"Found existing ralph progress. Resume?",
|
||||
"Found existing ralpi progress. Resume?",
|
||||
["Yes, resume", "No, start fresh"],
|
||||
);
|
||||
|
||||
@@ -348,7 +350,7 @@ async function handleRun(
|
||||
|
||||
// Set initial status
|
||||
ctx.ui.setStatus(
|
||||
"ralph",
|
||||
"ralpi",
|
||||
`Starting ${project.tasks.length} tasks from ${path.basename(taskFile)}`,
|
||||
);
|
||||
|
||||
@@ -384,7 +386,7 @@ async function handleRun(
|
||||
ctx.ui.notify(output, "info");
|
||||
}
|
||||
|
||||
// ─── /ralph status ───────────────────────────────────────────────────────────
|
||||
// ─── /ralpi status ───────────────────────────────────────────────────────────
|
||||
|
||||
async function handleStatus(
|
||||
ctx: ExtensionContext,
|
||||
@@ -408,7 +410,7 @@ async function handleStatus(
|
||||
ctx.ui.notify(
|
||||
`No progress for ${path.basename(taskFile)}. ${
|
||||
project.tasks.length
|
||||
} tasks found.\nUse /ralph run ${args[0]} to start.`,
|
||||
} tasks found.\nUse /ralpi run ${args[0]} to start.`,
|
||||
"info",
|
||||
);
|
||||
return;
|
||||
@@ -417,7 +419,7 @@ async function handleStatus(
|
||||
const found = findProgressFile(process.cwd());
|
||||
if (!found) {
|
||||
ctx.ui.notify(
|
||||
"No .ralph/progress.json found. Start with /ralph run [task-file]",
|
||||
"No .ralpi/progress.json found. Start with /ralpi run [task-file]",
|
||||
"warning",
|
||||
);
|
||||
return;
|
||||
@@ -426,7 +428,7 @@ async function handleStatus(
|
||||
ctx.ui.notify(formatAllPRDsStatus(found.state), "info");
|
||||
}
|
||||
|
||||
// ─── /ralph resume ───────────────────────────────────────────────────────────
|
||||
// ─── /ralpi resume ───────────────────────────────────────────────────────────
|
||||
|
||||
async function handleResume(
|
||||
ctx: ExtensionContext,
|
||||
@@ -442,7 +444,7 @@ async function handleResume(
|
||||
found = findProgressFile(process.cwd(), taskFile);
|
||||
if (!found) {
|
||||
ctx.ui.notify(
|
||||
`No existing progress for ${args[0]}. Start with /ralph run ${args[0]}`,
|
||||
`No existing progress for ${args[0]}. Start with /ralpi run ${args[0]}`,
|
||||
"warning",
|
||||
);
|
||||
return;
|
||||
@@ -452,7 +454,7 @@ async function handleResume(
|
||||
found = findProgressFile(process.cwd());
|
||||
if (!found) {
|
||||
ctx.ui.notify(
|
||||
"No .ralph/progress.json found. Start with /ralph run [task-file]",
|
||||
"No .ralpi/progress.json found. Start with /ralpi run [task-file]",
|
||||
"warning",
|
||||
);
|
||||
return;
|
||||
@@ -476,7 +478,7 @@ async function handleResume(
|
||||
progress.setPaused(false);
|
||||
|
||||
// Set resume status
|
||||
ctx.ui.setStatus("ralph", `Resuming from ${path.basename(taskFile)}`);
|
||||
ctx.ui.setStatus("ralpi", `Resuming from ${path.basename(taskFile)}`);
|
||||
|
||||
const completed = buildCompletedSet(progress, project);
|
||||
const mode = await selectExecutionMode(ctx, project, taskFile);
|
||||
@@ -497,7 +499,7 @@ async function handleResume(
|
||||
ctx.ui.notify(formatProgressStatus(progress.getState()), "info");
|
||||
}
|
||||
|
||||
// ─── /ralph next ─────────────────────────────────────────────────────────────
|
||||
// ─── /ralpi next ─────────────────────────────────────────────────────────────
|
||||
|
||||
async function handleNext(
|
||||
ctx: ExtensionContext,
|
||||
@@ -520,7 +522,7 @@ async function handleNext(
|
||||
found = findProgressFile(process.cwd());
|
||||
if (!found) {
|
||||
ctx.ui.notify(
|
||||
"No .ralph/progress.json found. Start with /ralph run [task-file]",
|
||||
"No .ralpi/progress.json found. Start with /ralpi run [task-file]",
|
||||
"warning",
|
||||
);
|
||||
return;
|
||||
@@ -578,7 +580,7 @@ async function handleNext(
|
||||
);
|
||||
}
|
||||
|
||||
// ─── /ralph reset ────────────────────────────────────────────────────────────
|
||||
// ─── /ralpi reset ────────────────────────────────────────────────────────────
|
||||
|
||||
async function handleReset(
|
||||
ctx: ExtensionContext,
|
||||
@@ -596,7 +598,7 @@ async function handleReset(
|
||||
const found = findProgressFile(process.cwd());
|
||||
if (!found) {
|
||||
ctx.ui.notify(
|
||||
"No .ralph/progress.json found. Start with /ralph run [task-file]",
|
||||
"No .ralpi/progress.json found. Start with /ralpi run [task-file]",
|
||||
"warning",
|
||||
);
|
||||
return;
|
||||
|
||||
65
package-lock.json
generated
65
package-lock.json
generated
@@ -1,65 +0,0 @@
|
||||
{
|
||||
"name": "ralph-loop",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ralph-loop",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"yaml": "^2.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"typescript": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.19.41",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz",
|
||||
"integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
|
||||
"integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/eemeli"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
package.json
12
package.json
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "ralph-loop",
|
||||
"name": "ralpi-loop",
|
||||
"version": "1.0.0",
|
||||
"description": "Execute tasks from task files using DAG-based dependency resolution with persistent progress tracking",
|
||||
"main": "dist/index.js",
|
||||
@@ -8,7 +8,9 @@
|
||||
"pi-extension",
|
||||
"task-runner",
|
||||
"dag",
|
||||
"task-manager"
|
||||
"task-manager",
|
||||
"ralpi-loop",
|
||||
"prd"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
@@ -24,9 +26,9 @@
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"pi": {
|
||||
"extensions": ["./dist/index.js"],
|
||||
"skills": ["./skills"],
|
||||
"prompts": ["./prompts"]
|
||||
"extensions": [
|
||||
"./dist/index.js"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"yaml": "^2.4.0"
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
# ralph-task
|
||||
---
|
||||
description: Executes individual tasks from ralpi task files using DAG-based dependency resolution, with progress tracking and reflection support
|
||||
---
|
||||
|
||||
Execute a single task from a ralph task file.
|
||||
# ralpi-task
|
||||
|
||||
Execute a single task from a ralpi task file.
|
||||
|
||||
## When to Use
|
||||
|
||||
@@ -11,9 +15,9 @@ Execute a single task from a ralph task file.
|
||||
## Usage
|
||||
|
||||
```
|
||||
/ralph run [task-file] # Run all tasks
|
||||
/ralph next [task-file] # Run next batch
|
||||
/ralph status [task-file] # Check progress
|
||||
/ralpi run [task-file] # Run all tasks
|
||||
/ralpi next [task-file] # Run next batch
|
||||
/ralpi status [task-file] # Check progress
|
||||
```
|
||||
|
||||
## Task File Location
|
||||
@@ -1,11 +1,17 @@
|
||||
import type { RalphConfig } from "./types";
|
||||
import { DEFAULT_CONFIG } from "./types";
|
||||
|
||||
export { DEFAULT_CONFIG };
|
||||
|
||||
// CLI
|
||||
export const SLASH_COMMAND = "/ralph";
|
||||
export const COMMANDS = ["run", "plan", "status", "resume", "next", "reset"] as const;
|
||||
export const SLASH_COMMAND = "/ralpi";
|
||||
export const COMMANDS = [
|
||||
"run",
|
||||
"plan",
|
||||
"status",
|
||||
"resume",
|
||||
"next",
|
||||
"reset",
|
||||
] as const;
|
||||
|
||||
// Task file detection
|
||||
export const TASK_FILE_NAMES = [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as path from "node:path";
|
||||
import type { Task, Project, Reflection, ToolUsage } from "./types";
|
||||
import type { RalphConfig } from "./types";
|
||||
import type { RalpiConfig } from "./types";
|
||||
import type { ProgressTracker } from "./progress";
|
||||
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
||||
import { buildTaskPrompt } from "./prompts";
|
||||
@@ -35,7 +35,7 @@ export interface ToolCallEntry {
|
||||
export async function runTask(
|
||||
task: Task,
|
||||
project: Project,
|
||||
config: RalphConfig,
|
||||
config: RalpiConfig,
|
||||
depReflections: Reflection[],
|
||||
ctx: ExtensionContext,
|
||||
sendChatMessage?: SendChatMessage,
|
||||
@@ -62,14 +62,14 @@ export async function runTask(
|
||||
config.prompts.projectContext,
|
||||
);
|
||||
|
||||
// Write prompt to .ralph/ with timestamp (for debugging)
|
||||
const ralphDir = path.join(projectDir, ".ralph");
|
||||
ensureDir(ralphDir);
|
||||
const promptFile = path.join(ralphDir, `prompt-${startMs}.md`);
|
||||
// Write prompt to .ralpi/ with timestamp (for debugging)
|
||||
const ralpiDir = path.join(projectDir, ".ralpi");
|
||||
ensureDir(ralpiDir);
|
||||
const promptFile = path.join(ralpiDir, `prompt-${startMs}.md`);
|
||||
writeFileSafe(promptFile, prompt);
|
||||
|
||||
// Footer shows just the task title (no batch prefix)
|
||||
ctx.ui.setStatus("ralph", task.title);
|
||||
ctx.ui.setStatus("ralpi", task.title);
|
||||
|
||||
const taskHeader = `${task.id} · ${task.title}`;
|
||||
|
||||
@@ -77,7 +77,7 @@ export async function runTask(
|
||||
// Using setWidget instead of setWorkingMessage because the working message area
|
||||
// is only visible during parent agent streaming, not during extension command execution.
|
||||
// Widget key is unique per task so parallel tasks each get their own widget.
|
||||
const widgetKey = `ralph-task-${task.id}`;
|
||||
const widgetKey = `ralpi-task-${task.id}`;
|
||||
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
||||
let frameIndex = 0;
|
||||
const theme = ctx.ui.theme;
|
||||
@@ -126,7 +126,7 @@ export async function runTask(
|
||||
const timeoutMs = task.timeoutMs ?? config.execution.timeoutMs;
|
||||
|
||||
// Pre-create session file path so events stream to disk (avoids 300+ MB in-memory accumulation)
|
||||
const sessionsDir = path.join(ralphDir, "sessions");
|
||||
const sessionsDir = path.join(ralpiDir, "sessions");
|
||||
ensureDir(sessionsDir);
|
||||
const sessionFilePath = path.join(sessionsDir, `${task.id}-${startMs}.txt`);
|
||||
|
||||
@@ -158,7 +158,7 @@ export async function runTask(
|
||||
} else {
|
||||
ctx.ui.setWidget(widgetKey, undefined);
|
||||
}
|
||||
ctx.ui.setStatus("ralph", undefined);
|
||||
ctx.ui.setStatus("ralpi", undefined);
|
||||
|
||||
if (!output.success) {
|
||||
sendChatMessage?.(`✗ ${taskHeader} — ${output.error}`);
|
||||
@@ -213,7 +213,7 @@ export async function runTask(
|
||||
export async function executeBatch(
|
||||
tasks: Task[],
|
||||
project: Project,
|
||||
config: RalphConfig,
|
||||
config: RalpiConfig,
|
||||
progress: ProgressTracker,
|
||||
ctx: ExtensionContext,
|
||||
options?: { parallel?: boolean },
|
||||
@@ -264,7 +264,7 @@ export async function executeBatch(
|
||||
async function executeBatchParallel(
|
||||
tasks: Task[],
|
||||
project: Project,
|
||||
config: RalphConfig,
|
||||
config: RalpiConfig,
|
||||
progress: ProgressTracker,
|
||||
ctx: ExtensionContext,
|
||||
sendChatMessage?: SendChatMessage,
|
||||
@@ -310,7 +310,7 @@ async function executeBatchParallel(
|
||||
async function executeTask(
|
||||
task: Task,
|
||||
project: Project,
|
||||
config: RalphConfig,
|
||||
config: RalpiConfig,
|
||||
progress: ProgressTracker,
|
||||
ctx: ExtensionContext,
|
||||
sendChatMessage?: SendChatMessage,
|
||||
@@ -390,7 +390,7 @@ async function executeTask(
|
||||
|
||||
function saveReflectionToFile(
|
||||
sourceDir: string,
|
||||
config: RalphConfig,
|
||||
config: RalpiConfig,
|
||||
reflection: Reflection,
|
||||
): void {
|
||||
const reflectionsDir = path.join(sourceDir, config.paths.reflectionsDir);
|
||||
@@ -421,7 +421,10 @@ function formatToolArg(name: string, args: unknown): string {
|
||||
case "edit":
|
||||
return truncateMiddle(String(a.path ?? ""), 60);
|
||||
case "grep":
|
||||
return `${a.pattern ?? "?"} — ${truncateMiddle(String(a.path ?? ""), 40)}`;
|
||||
return `${a.pattern ?? "?"} — ${truncateMiddle(
|
||||
String(a.path ?? ""),
|
||||
40,
|
||||
)}`;
|
||||
case "find":
|
||||
return `${a.path ?? "."} — ${a.glob ?? "*"}`;
|
||||
case "ls":
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import type { ProgressState, PRDProgress, Task, Reflection, ToolUsage } from "./types";
|
||||
import type {
|
||||
ProgressState,
|
||||
PRDProgress,
|
||||
Task,
|
||||
Reflection,
|
||||
ToolUsage,
|
||||
} from "./types";
|
||||
import { ensureDir } from "./utils";
|
||||
|
||||
/**
|
||||
@@ -9,12 +15,15 @@ import { ensureDir } from "./utils";
|
||||
*/
|
||||
export function derivePRDKey(projectDir: string, sourcePath: string): string {
|
||||
const rel = path.relative(projectDir, sourcePath);
|
||||
return rel.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
||||
return rel
|
||||
.replace(/[^a-zA-Z0-9_-]/g, "-")
|
||||
.replace(/-+/g, "-")
|
||||
.replace(/^-|-$/g, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages persistent progress state for a ralph execution.
|
||||
* State is stored as JSON in .ralph/progress.json.
|
||||
* State is stored as JSON in .ralpi/progress.json.
|
||||
* Supports multiple PRDs in progress simultaneously via the `prds` field.
|
||||
* Falls back to legacy flat format for backward compatibility.
|
||||
*/
|
||||
@@ -24,7 +33,7 @@ export class ProgressTracker {
|
||||
private prdKey: string;
|
||||
|
||||
constructor(projectDir: string, sourcePath: string, prdKey?: string) {
|
||||
const stateDir = path.join(projectDir, ".ralph");
|
||||
const stateDir = path.join(projectDir, ".ralpi");
|
||||
ensureDir(stateDir);
|
||||
this.statePath = path.join(stateDir, "progress.json");
|
||||
this.prdKey = prdKey ?? derivePRDKey(projectDir, sourcePath);
|
||||
@@ -66,7 +75,10 @@ export class ProgressTracker {
|
||||
}
|
||||
|
||||
// Legacy flat state exists but for a different source — promote it to PRD mode
|
||||
const legacyKey = derivePRDKey(path.dirname(this.statePath), parsed.sourcePath);
|
||||
const legacyKey = derivePRDKey(
|
||||
path.dirname(this.statePath),
|
||||
parsed.sourcePath,
|
||||
);
|
||||
parsed.prds = {
|
||||
[legacyKey]: {
|
||||
sourcePath: parsed.sourcePath,
|
||||
|
||||
10
src/types.ts
10
src/types.ts
@@ -137,9 +137,9 @@ export interface PRDProgress {
|
||||
|
||||
// ─── Configuration ────────────────────────────────────────────────────────────
|
||||
|
||||
export interface RalphConfig {
|
||||
export interface RalpiConfig {
|
||||
paths: {
|
||||
/** Directory for ralph state files */
|
||||
/** Directory for ralpi state files */
|
||||
stateDir: string;
|
||||
/** Directory for per-task reflections */
|
||||
reflectionsDir: string;
|
||||
@@ -162,10 +162,10 @@ export interface RalphConfig {
|
||||
};
|
||||
}
|
||||
|
||||
export const DEFAULT_CONFIG: RalphConfig = {
|
||||
export const DEFAULT_CONFIG: RalpiConfig = {
|
||||
paths: {
|
||||
stateDir: ".ralph",
|
||||
reflectionsDir: ".ralph/reflections",
|
||||
stateDir: ".ralpi",
|
||||
reflectionsDir: ".ralpi/reflections",
|
||||
},
|
||||
execution: {
|
||||
maxRetries: 3,
|
||||
|
||||
18
src/utils.ts
18
src/utils.ts
@@ -1,7 +1,7 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import type {
|
||||
RalphConfig,
|
||||
RalpiConfig,
|
||||
PRDProgress,
|
||||
ProgressState,
|
||||
ToolUsage,
|
||||
@@ -39,7 +39,7 @@ export function writeFileSafe(filePath: string, content: string): void {
|
||||
// ─── Progress Discovery ─────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Find the nearest .ralph/progress.json by walking up from the given directory.
|
||||
* Find the nearest .ralpi/progress.json by walking up from the given directory.
|
||||
* For a specific sourcePath, finds the matching PRD entry.
|
||||
*/
|
||||
export function findProgressFile(
|
||||
@@ -50,7 +50,7 @@ export function findProgressFile(
|
||||
const root = path.parse(current).root;
|
||||
|
||||
while (current !== root) {
|
||||
const candidate = path.join(current, ".ralph", "progress.json");
|
||||
const candidate = path.join(current, ".ralpi", "progress.json");
|
||||
if (fs.existsSync(candidate)) {
|
||||
try {
|
||||
const raw = fs.readFileSync(candidate, "utf-8");
|
||||
@@ -113,9 +113,9 @@ function parseSimpleYaml(content: string): Record<string, any> {
|
||||
* Deep merge configuration objects
|
||||
*/
|
||||
function mergeConfig(
|
||||
defaults: RalphConfig,
|
||||
defaults: RalpiConfig,
|
||||
overrides: Record<string, any>,
|
||||
): RalphConfig {
|
||||
): RalpiConfig {
|
||||
const result = { ...defaults };
|
||||
|
||||
for (const [key, value] of Object.entries(overrides)) {
|
||||
@@ -126,14 +126,14 @@ function mergeConfig(
|
||||
}
|
||||
}
|
||||
|
||||
return result as RalphConfig;
|
||||
return result as RalpiConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration from .ralph/config.yaml or return defaults
|
||||
* Load configuration from .ralpi/config.yaml or return defaults
|
||||
*/
|
||||
export function loadConfig(projectDir: string): RalphConfig {
|
||||
const configPath = path.join(projectDir, ".ralph", "config.yaml");
|
||||
export function loadConfig(projectDir: string): RalpiConfig {
|
||||
const configPath = path.join(projectDir, ".ralpi", "config.yaml");
|
||||
|
||||
// Return defaults silently when config file does not exist
|
||||
if (!fs.existsSync(configPath)) {
|
||||
|
||||
Reference in New Issue
Block a user