Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,6 @@ __marimo__/
tmp/

/.gemini/tmp/

# JavaScript
node_modules/
1 change: 1 addition & 0 deletions GEMINI.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ To find a file (e.g., "**Product Definition**") within a specific context (Proje
- **Product Guidelines**: `conductor/product-guidelines.md`
- **Tracks Registry**: `conductor/tracks.md`
- **Tracks Directory**: `conductor/tracks/`
- **Ralph Loop State**: `conductor/.ralph-state.json` (optional)

**Standard Default Paths (Track):**
- **Specification**: `conductor/tracks/<track_id>/spec.md`
Expand Down
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,22 @@ Conductor will:
3. Update the status in the plan as it progresses.
4. **Verify Progress**: Guide you through a manual verification step at the end of each phase to ensure everything works as expected.

#### Ralph Mode (Autonomous Loop)
Ralph Mode is a functionality based on the Geoffrey Huntley's Ralph loop technique for the Gemini CLI that enables continuous autonomous development cycles. It allows the agent to iteratively improve your project until completion, following an automated Red-Green-Refactor loop with built-in safeguards to prevent infinite loops.

```bash
/conductor:implement --ralph
```
* `--max-iterations=N`: Change the retry limit (default: 10).
* `--completion-word=WORD`: Change the work completion magic word (default: TRACK_COMPLETE).

> [!NOTE]
> For a seamless autonomous experience, you may enable `accepts-edits` or YOLO mode in your configuration.

> [!WARNING]
> Using Gemini CLI in YOLO mode allows the agent to modify files and use tools without explicit confirmation and authorization from the user.


During implementation, you can also:

- **Check status**: Get a high-level overview of your project's progress.
Expand All @@ -113,7 +129,7 @@ During implementation, you can also:
| :--- | :--- | :--- |
| `/conductor:setup` | Scaffolds the project and sets up the Conductor environment. Run this once per project. | `conductor/product.md`<br>`conductor/product-guidelines.md`<br>`conductor/tech-stack.md`<br>`conductor/workflow.md`<br>`conductor/tracks.md` |
| `/conductor:newTrack` | Starts a new feature or bug track. Generates `spec.md` and `plan.md`. | `conductor/tracks/<id>/spec.md`<br>`conductor/tracks/<id>/plan.md`<br>`conductor/tracks.md` |
| `/conductor:implement` | Executes the tasks defined in the current track's plan. | `conductor/tracks.md`<br>`conductor/tracks/<id>/plan.md` |
| /conductor:implement | Executes the tasks defined in the current track's plan. Use `--ralph` for autonomous TDD loop. | `conductor/tracks.md`<br>`conductor/tracks/<id>/plan.md` |
| `/conductor:status` | Displays the current progress of the tracks file and active tracks. | Reads `conductor/tracks.md` |
| `/conductor:revert` | Reverts a track, phase, or task by analyzing git history. | Reverts git history |
| `/conductor:review` | Reviews completed work against guidelines and the plan. | Reads `plan.md`, `product-guidelines.md` |
Expand Down
29 changes: 24 additions & 5 deletions commands/conductor/implement.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ CRITICAL: You must validate the success of every tool call. If any tool call fai

---

## 1.0.1 RALPH INITIALIZATION
**PROTOCOL: If autonomous mode is requested, initialize the Ralph loop.**

1. **Check for Ralph Flag:** Check if the user provided the `--ralph` flag in their command.
2. **Initialize Loop:** If (and ONLY if) the `--ralph` flag is present:
- You MUST immediately call the tool `ralph_start` with the following arguments:
- `prompt`: The full text of the user's current request.
- `maxIterations`: The value of the `--max-iterations` flag if provided, otherwise 10.
- `completionWord`: The value of the `--completion-word` flag if provided, otherwise "TRACK_COMPLETE".
- **CRITICAL:** Do NOT proceed to any other steps until this tool call returns successfully.

---

## 1.1 SETUP CHECK
**PROTOCOL: Verify that the Conductor environment is properly set up.**

Expand All @@ -15,7 +28,9 @@ CRITICAL: You must validate the success of every tool call. If any tool call fai
- **Tech Stack**
- **Workflow**

2. **Handle Failure:** If ANY of these are missing (or their resolved paths do not exist), Announce: "Conductor is not set up. Please run `/conductor:setup`." and HALT.
2. **Handle Failure:** If ANY of these are missing (or their resolved paths do not exist):
- If in Ralph Mode: Call `ralph_end` with `status='FAILURE'` and `message='Conductor is not set up.'`.
- Otherwise: Announce: "Conductor is not set up. Please run `/conductor:setup`." and HALT.


---
Expand All @@ -28,7 +43,9 @@ CRITICAL: You must validate the success of every tool call. If any tool call fai
2. **Locate and Parse Tracks Registry:**
- Resolve the **Tracks Registry**.
- Read and parse this file. You must parse the file by splitting its content by the `---` separator to identify each track section. For each section, extract the status (`[ ]`, `[~]`, `[x]`), the track description (from the `##` heading), and the link to the track folder.
- **CRITICAL:** If no track sections are found after parsing, announce: "The tracks file is empty or malformed. No tracks to implement." and halt.
- **CRITICAL:** If no track sections are found after parsing:
- If in Ralph Mode: Call `ralph_end` with `status='FAILURE'` and `message='Tracks file is empty or malformed.'`.
- Otherwise: Announce: "The tracks file is empty or malformed. No tracks to implement." and halt.

3. **Continue:** Immediately proceed to the next step to select a track.

Expand All @@ -43,8 +60,8 @@ CRITICAL: You must validate the success of every tool call. If any tool call fai
- Announce: "No track name provided. Automatically selecting the next incomplete track: '<track_description>'."
- Proceed with this track.
3. **If no incomplete tracks are found:**
- Announce: "No incomplete tracks found in the tracks file. All tasks are completed!"
- Halt the process and await further user instructions.
- If in Ralph Mode: Call `ralph_end` with `status='SUCCESS'` and `message='All tracks completed.'`.
- Otherwise: Announce: "No incomplete tracks found in the tracks file. All tasks are completed!" and halt.

5. **Handle No Selection:** If no track is selected, inform the user and await further instructions.

Expand All @@ -64,7 +81,9 @@ CRITICAL: You must validate the success of every tool call. If any tool call fai
b. **Read Files:**
- **Track Context:** Using the **Universal File Resolution Protocol**, resolve and read the **Specification** and **Implementation Plan** for the selected track.
- **Workflow:** Resolve **Workflow** (via the **Universal File Resolution Protocol** using the project's index file).
c. **Error Handling:** If you fail to read any of these files, you MUST stop and inform the user of the error.
c. **Error Handling:** If you fail to read any of these files:
- If in Ralph Mode: Call `ralph_end` with `status='FAILURE'` and `message='Failed to read track context files.'`.
- Otherwise: Stop and inform the user of the error.

4. **Execute Tasks and Update Track Plan:**
a. **Announce:** State that you will now execute the tasks from the track's **Implementation Plan** by following the procedures in the **Workflow**.
Expand Down
10 changes: 8 additions & 2 deletions gemini-extension.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
{
"name": "conductor",
"version": "0.2.0",
"contextFileName": "GEMINI.md"
}
"contextFileName": "GEMINI.md",
"mcpServers": {
"conductorServer": {
"command": "node",
"args": ["${extensionPath}/mcp-server/dist/index.js"]
}
}
}
26 changes: 26 additions & 0 deletions hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"hooks": {
"AfterTool": [
{
"matcher": "ralph_start",
"hooks": [
{
"type": "command",
"command": "node ${extensionPath}/hooks/ralph-mode/setup.js",
"name": "Ralph Mode Initializer"
}
]
},
{
"matcher": "ralph_end",
"hooks": [
{
"type": "command",
"command": "node ${extensionPath}/hooks/ralph-mode/controller.js",
"name": "Ralph Mode Controller"
}
]
}
]
}
}
133 changes: 133 additions & 0 deletions hooks/ralph-mode/controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
const fs = require('fs');
const path = require('path');

/**
* MANAGES THE AUTONOMOUS RALPH LOOP VIA AfterTool HOOK.
*/

const STATE_FILE = path.join(process.cwd(), 'conductor', '.ralph-state.json');

async function main() {
// State File Missing (Quiet Exit)
if (!fs.existsSync(STATE_FILE)) {
console.log(JSON.stringify({}));
return;
}

// Read & Parse Input
let context;
try {
context = JSON.parse(fs.readFileSync(0, 'utf8'));
} catch (e) {
console.log(JSON.stringify({}));
return;
}

const { hook_event_name, tool_name, tool_input } = context;

// Check if this is the end tool
if (hook_event_name !== 'AfterTool' || !tool_name.endsWith('ralph_end')) {
console.log(JSON.stringify({}));
return;
}

// Load current Ralph loop state
let state;
try {
state = JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
} catch (e) {
console.log(JSON.stringify({}));
return;
}

const { status, message } = tool_input;

// Case: SUCCESS
if (status === 'SUCCESS') {
try {
fs.unlinkSync(STATE_FILE);
} catch (e) {
console.error(`[Ralph] Warning: Could not delete state file: ${e.message}`);
}

console.log(JSON.stringify({
decision: "allow",
systemMessage: "✅ Ralph: Cycle complete. State cleaned.",
}));
return;
}

// Case: FAILURE / RETRY LOOP

// Check Iteration Limit
if (state.iteration >= state.maxIterations) {
try {
fs.unlinkSync(STATE_FILE);
} catch (e) {
console.error(`[Ralph] Warning: Could not delete state file after max iterations: ${e.message}`);
}
console.log(JSON.stringify({
decision: "deny",
reason: "Stopping Ralph loop. Max iterations reached.",
systemMessage: "🛑 Ralph: Max iterations reached. Stopping loop."
}));
return;
}

// Increment Iteration for the next turn
state.iteration += 1;
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));

// Load Directive
let directiveContent = "";
try {
const directivePath = path.join(__dirname, 'directive.md');
directiveContent = fs.readFileSync(directivePath, 'utf8');
} catch (e) {
console.log(JSON.stringify({
decision: "deny",
reason: `CRITICAL: Ralph Mode controller failed. Could not read directive file (${e.message}). Aborting loop.`,
systemMessage: "🛑 Ralph Controller Failed: Missing directive."
}));
return;
}


// Prepare Re-injection Context
const directive = directiveContent.replace('{{COMPLETION_WORD}}', state.completionWord);

const failureContext = `
[RALPH SYSTEM MESSAGE]:
The previous session context was cleared to ensure focus.
LAST STATUS: ${status} (Iteration ${state.iteration - 1} of ${state.maxIterations})
REASON: ${message || "No specific reason provided."}

ACTION REQUIRED:
1. **RE-ORIENT:** Analyze the current project state (git status, diff, logs).
2. **FIX:** Modify the code, tests, or **Strategy** based on the failure reason.
3. **VERIFY:** Run tests again.
4. **FINALIZE:** Call 'ralph_end' with status 'SUCCESS' only when all tests pass and requirements are met.
`;

const fullContextPrompt = `
${state.originalPrompt || ""}

---
${directive}
---

${failureContext}
`;

// FORCE RETRY
console.log(JSON.stringify({
decision: "deny",
reason: fullContextPrompt, // Re-injects full context prompt.
systemMessage: `🔄 Ralph: ${status} detected. Auto-retrying (Iter ${state.iteration}/${state.maxIterations})...`
}));
}

main().catch(err => {
console.error(`[Ralph Controller] Fatal: ${err.message}`);
process.exit(1);
});
22 changes: 22 additions & 0 deletions hooks/ralph-mode/directive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## 🔴 RALPH MODE ACTIVE: TASK EXECUTION OVERRIDE
**ATTENTION:** Announce verbatim
> 🔁 Operating in **RALPH MODE** autonomous loop.

**INSTRUCTIONS:**
1. **Initialization Hygiene:** Ensure the **Ralph Loop State** (resolved via the **Universal File Resolution Protocol**) is excluded from version control and the agent's context. Add it to the appropriate ignore files for the active VCS (e.g., .gitignore, .hgignore) and tool configuration (e.g., .geminiignore) if not already present.
2. **Execute Standard Protocol:** You MUST follow Steps **3.1**, **3.2**, **3.3**, and **3.5** of the "TRACK IMPLEMENTATION" protocol exactly as defined in the prompt.
3. **Shell Command Execution:** Use flags that reject or prevent interactive user input (e.g., "-n", "--no-input").
4. **AUTONOMOUS CYCLE:**
- For each task, execute this **RALPH CYCLE**:
1. **RED:** Write failing tests based on the **Specification**.
2. **GREEN:** Implement code to pass tests.
3. **VERIFY:** Run tests.
- **IF FAIL:** Attempt to fix and re-run. If still failing, call 'ralph_end' with status='FAILURE' and include the error details in the message.
- **PASS:** Proceed to the next task.
4. **RECORD:** Update plan status to [x] and commit changes.
5. **SKIP MANUAL TASKS:** Mark any task involving "Manual Verification" or "User Feedback" as [x] immediately and proceed. Ralph's tests are the only source of truth.
6. **COMPLETION:**
- **WHEN DONE:** If all tests pass and the track is [x]:
1. Call 'ralph_end' with status='SUCCESS' and message='Task complete: {{COMPLETION_WORD}}'.
2. **AFTER THE TOOL:** You will receive a confirmation message. **IMMEDIATELY** proceed to **Section 5.0** (Track Cleanup) and prompt the user.
- **IF STUCK:** If you are unable to proceed due to ambiguity or tool failures, call 'ralph_end' with status='STUCK' and explain why.
85 changes: 85 additions & 0 deletions hooks/ralph-mode/setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
const fs = require('fs');
const path = require('path');

/**
* INITIALIZES RALPH MODE AFTER THE START TOOL IS CALLED.
**/

const STATE_FILE = path.join(process.cwd(), 'conductor', '.ralph-state.json');

async function main() {
// Read Input
let context;
try {
context = JSON.parse(fs.readFileSync(0, 'utf8'));
} catch (e) {
console.log(JSON.stringify({}));
return;
}

const { hook_event_name, tool_name, tool_input } = context;

// Check if this is the start tool
if (hook_event_name !== 'AfterTool' || !tool_name.endsWith('ralph_start')) {
console.log(JSON.stringify({}));
return;
}

// --- RALPH MODE DETECTED ---

const prompt = tool_input.prompt || "";
const completionWord = tool_input.completionWord || 'TRACK_COMPLETE';
const maxIterations = tool_input.maxIterations || 10;

// Create State File
const state = {
completionWord: completionWord,
maxIterations: maxIterations,
iteration: 1,
startedAt: new Date().toISOString(),
originalPrompt: prompt,
};

try {
const dir = path.dirname(STATE_FILE);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
} catch (e) {
console.log(JSON.stringify({
decision: "deny",
reason: `CRITICAL: Ralph Mode failed to initialize. Could not create state file (${e.message}). Aborting autonomous mode.`,
systemMessage: "🛑 Ralph Init Failed: File system error."
}));
return;
}

// Inject Directive
let directiveContent = "";
try {
const directivePath = path.join(__dirname, 'directive.md');
directiveContent = fs.readFileSync(directivePath, 'utf8');
} catch (e) {
console.log(JSON.stringify({
decision: "deny",
reason: `CRITICAL: Ralph Mode failed to initialize. (${e.message}). Aborting.`,
systemMessage: "🛑 Ralph Init Failed: Missing directive."
}));
return;
}

const directive = directiveContent.replace('{{COMPLETION_WORD}}', completionWord);

// Output the JSON response
console.log(JSON.stringify({
hookSpecificOutput: {
hookEventName: "AfterTool",
additionalContext: directive
},
systemMessage: "🔴 Ralph Mode Activated: TDD Loop Engaged.",
}));
}

main().catch(err => {
console.error(`[Ralph Init] Fatal: ${err.message}`);
process.exit(1);
});
Loading
Loading