Sync: Complete project state with all MEGA SPRINT V1-V3 features and Codex stubs
This commit is contained in:
11
ralph/.gitignore
vendored
Normal file
11
ralph/.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
config/*.local.json
|
||||
runs/*
|
||||
!runs/.gitkeep
|
||||
logs/*
|
||||
!logs/.gitkeep
|
||||
state/*.json
|
||||
state/*.jsonl
|
||||
state/*.md
|
||||
!state/.gitkeep
|
||||
worktrees/*
|
||||
!worktrees/.gitkeep
|
||||
176
ralph/README.md
Normal file
176
ralph/README.md
Normal file
@@ -0,0 +1,176 @@
|
||||
# Ralph
|
||||
|
||||
Ralph is a local swarm runner for this Ableton MCP project.
|
||||
|
||||
It is built for the stack that already exists on this machine:
|
||||
|
||||
- `codex` authenticated via app login
|
||||
- `claude -p` routed to Anthropic-compatible providers
|
||||
- Windows native PowerShell
|
||||
- this repository as the shared source of truth
|
||||
|
||||
## Design
|
||||
|
||||
Ralph does not try to make every model edit the same tree at once.
|
||||
|
||||
The default flow is:
|
||||
|
||||
1. create a task pack in `ralph/tasks/current/`
|
||||
2. create a dedicated implementer worktree
|
||||
3. run one implementer model against that worktree
|
||||
4. run multiple reviewers against the resulting diff
|
||||
5. run `codex exec resume` against the persistent Codex master session
|
||||
6. optionally run a fix pass in the same implementer worktree
|
||||
7. leave a run folder with prompts, outputs, reviews and diffs
|
||||
|
||||
## Default roles
|
||||
|
||||
- Implementer: `opencode_glm5`
|
||||
- Reviewer 1: `opencode_qwen3coder_plus`
|
||||
- Reviewer 2: `opencode_glm47`
|
||||
- Codex master: persistent session reviewer and sprint writer
|
||||
|
||||
## Important safety rule
|
||||
|
||||
Ralph does not auto-merge into the main working tree.
|
||||
|
||||
It creates a worktree under `ralph/worktrees/` and leaves the result there for review or manual cherry-pick.
|
||||
|
||||
## Local config
|
||||
|
||||
Sensitive config is stored in:
|
||||
|
||||
- `ralph/config/providers.local.json`
|
||||
- `ralph/config/codex.local.json`
|
||||
|
||||
Both are gitignored.
|
||||
|
||||
## Quick start
|
||||
|
||||
Smoke test providers:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Test-RalphProviders.ps1
|
||||
```
|
||||
|
||||
Smoke test Codex master:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Test-RalphCodex.ps1
|
||||
```
|
||||
|
||||
Run one autonomous pass on the current task pack:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Start-RalphAutopilot.ps1
|
||||
```
|
||||
|
||||
Start the localhost dashboard:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Start-RalphDashboard.ps1
|
||||
```
|
||||
|
||||
Default URL:
|
||||
|
||||
```text
|
||||
http://127.0.0.1:8765
|
||||
```
|
||||
|
||||
Dry run only:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Start-RalphAutopilot.ps1 -DryRun
|
||||
```
|
||||
|
||||
Submit a single markdown task into the inbox:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Submit-RalphTask.ps1 `
|
||||
-SourceFile .\docs\SPRINT_v0.1.40_NEXT_GLM_OPEN_PROJECT_EDITING_AND_COHERENCE.md
|
||||
```
|
||||
|
||||
Run the inbox daemon in the foreground:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Start-RalphInboxDaemon.ps1
|
||||
```
|
||||
|
||||
Run the inbox daemon in the background:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Start-RalphInboxBackground.ps1
|
||||
```
|
||||
|
||||
Stop the inbox daemon:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Stop-RalphInboxDaemon.ps1
|
||||
```
|
||||
|
||||
Check daemon and queue status:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Get-RalphStatus.ps1
|
||||
```
|
||||
|
||||
Install the inbox daemon as a Windows Scheduled Task:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Install-RalphScheduledTask.ps1
|
||||
```
|
||||
|
||||
Run Codex master review only:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Invoke-CodexMaster.ps1 `
|
||||
-PromptFile .\ralph\tasks\current\TASK.md `
|
||||
-OutputFile .\ralph\state\codex_master_manual_review.md
|
||||
```
|
||||
|
||||
## Task pack
|
||||
|
||||
The current swarm task lives here:
|
||||
|
||||
- `ralph/tasks/current/TASK.md`
|
||||
- `ralph/tasks/current/ACCEPTANCE.md`
|
||||
- `ralph/tasks/current/CONTEXT.md`
|
||||
|
||||
Update those files before each autonomous run.
|
||||
|
||||
## Codex master session
|
||||
|
||||
Ralph is configured to reuse the persistent Codex session supplied by the user.
|
||||
|
||||
That gives Codex memory across reviews without needing an OpenAI API key.
|
||||
|
||||
## 24/7 inbox mode
|
||||
|
||||
Ralph can now run as a local queue processor:
|
||||
|
||||
- drop a single `.md` into `ralph/tasks/inbox/`
|
||||
- the daemon converts it into a task pack automatically
|
||||
- the implementer runs in an isolated worktree
|
||||
- the configured reviewers run next
|
||||
- Codex master performs the final review through `codex exec resume`
|
||||
- the run only counts as complete if Codex final verdict is `pass`
|
||||
|
||||
The queue folders are:
|
||||
|
||||
- `ralph/tasks/inbox`
|
||||
- `ralph/tasks/processing`
|
||||
- `ralph/tasks/completed`
|
||||
- `ralph/tasks/failed`
|
||||
|
||||
Recommended default routing:
|
||||
|
||||
- implementer: `opencode_glm5`
|
||||
- reviewers: `opencode_qwen3coder_plus`, `opencode_glm47`
|
||||
- final reviewer: persistent Codex master session
|
||||
|
||||
## Notes
|
||||
|
||||
- If a token was ever exposed outside this machine, rotate it.
|
||||
- If Ableton runtime work is involved, the swarm still has to validate against the real Live session and logs.
|
||||
- Provider quality is not treated as equal. `glm-5.1` remains the preferred reviewer.
|
||||
- The dashboard reads local state files and recent run folders. It does not execute runs on its own.
|
||||
10
ralph/config/automation.example.json
Normal file
10
ralph/config/automation.example.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"auto_followup": {
|
||||
"enabled": true,
|
||||
"trigger_on_failure": true,
|
||||
"trigger_on_success": true,
|
||||
"max_chain_depth": 6,
|
||||
"target_directory": "docs\\autopilot",
|
||||
"title_prefix": "AUTOFOLLOWUP"
|
||||
}
|
||||
}
|
||||
5
ralph/config/codex.example.json
Normal file
5
ralph/config/codex.example.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"session_id": "REPLACE_ME",
|
||||
"model": "gpt-5.4",
|
||||
"working_directory": "C:\\ProgramData\\Ableton\\Live 12 Suite\\Resources\\MIDI Remote Scripts"
|
||||
}
|
||||
104
ralph/config/providers.example.json
Normal file
104
ralph/config/providers.example.json
Normal file
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"default_implementer": "opencode_glm5",
|
||||
"default_reviewers": [
|
||||
"opencode_qwen3coder_plus",
|
||||
"opencode_glm47",
|
||||
"opencode_glm5_review"
|
||||
],
|
||||
"providers": {
|
||||
"opencode_glm5": {
|
||||
"runner": "opencode",
|
||||
"model": "bailian-coding-plan/glm-5",
|
||||
"timeout_ms": 3000000,
|
||||
"variant": "high"
|
||||
},
|
||||
"opencode_qwen3coder_plus": {
|
||||
"runner": "opencode",
|
||||
"model": "bailian-coding-plan/qwen3-coder-plus",
|
||||
"timeout_ms": 3000000,
|
||||
"variant": "high"
|
||||
},
|
||||
"opencode_glm47": {
|
||||
"runner": "opencode",
|
||||
"model": "bailian-coding-plan/glm-4.7",
|
||||
"timeout_ms": 3000000,
|
||||
"variant": "high"
|
||||
},
|
||||
"opencode_glm5_review": {
|
||||
"runner": "opencode",
|
||||
"model": "bailian-coding-plan/glm-5",
|
||||
"timeout_ms": 3000000,
|
||||
"variant": "high"
|
||||
},
|
||||
"opencode_qwen35": {
|
||||
"runner": "opencode",
|
||||
"model": "bailian-coding-plan/qwen3.5-plus",
|
||||
"timeout_ms": 3000000,
|
||||
"variant": "high"
|
||||
},
|
||||
"zai_glm51": {
|
||||
"runner": "claude",
|
||||
"base_url": "https://api.z.ai/api/anthropic",
|
||||
"auth_token": "REPLACE_ME",
|
||||
"model": "glm-5.1",
|
||||
"timeout_ms": 3000000,
|
||||
"experimental_agent_teams": true
|
||||
},
|
||||
"zai_glm47": {
|
||||
"runner": "claude",
|
||||
"base_url": "https://api.z.ai/api/anthropic",
|
||||
"auth_token": "REPLACE_ME",
|
||||
"model": "glm-4.7",
|
||||
"timeout_ms": 3000000,
|
||||
"experimental_agent_teams": false
|
||||
},
|
||||
"dashscope_glm5": {
|
||||
"runner": "claude",
|
||||
"base_url": "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic",
|
||||
"auth_token": "REPLACE_ME",
|
||||
"model": "glm-5",
|
||||
"timeout_ms": 3000000,
|
||||
"experimental_agent_teams": true
|
||||
},
|
||||
"dashscope_qwen35": {
|
||||
"runner": "claude",
|
||||
"base_url": "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic",
|
||||
"auth_token": "REPLACE_ME",
|
||||
"model": "qwen3.5-plus",
|
||||
"timeout_ms": 3000000,
|
||||
"experimental_agent_teams": false
|
||||
},
|
||||
"dashscope_qwen3coder_next": {
|
||||
"runner": "claude",
|
||||
"base_url": "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic",
|
||||
"auth_token": "REPLACE_ME",
|
||||
"model": "qwen3-coder-next",
|
||||
"timeout_ms": 3000000,
|
||||
"experimental_agent_teams": false
|
||||
},
|
||||
"dashscope_qwen3coder_plus": {
|
||||
"runner": "claude",
|
||||
"base_url": "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic",
|
||||
"auth_token": "REPLACE_ME",
|
||||
"model": "qwen3-coder-plus",
|
||||
"timeout_ms": 3000000,
|
||||
"experimental_agent_teams": false
|
||||
},
|
||||
"dashscope_minimax25": {
|
||||
"runner": "claude",
|
||||
"base_url": "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic",
|
||||
"auth_token": "REPLACE_ME",
|
||||
"model": "MiniMax-M2.5",
|
||||
"timeout_ms": 3000000,
|
||||
"experimental_agent_teams": true
|
||||
},
|
||||
"fireworks_kimi25": {
|
||||
"runner": "claude",
|
||||
"base_url": "https://api.fireworks.ai/inference",
|
||||
"auth_token": "REPLACE_ME",
|
||||
"model": "accounts/fireworks/routers/kimi-k2p5-turbo",
|
||||
"timeout_ms": 3000000,
|
||||
"experimental_agent_teams": true
|
||||
}
|
||||
}
|
||||
}
|
||||
25
ralph/config/telegram.example.json
Normal file
25
ralph/config/telegram.example.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"enabled": false,
|
||||
"prefix": "Ralph",
|
||||
"timeout_seconds": 8,
|
||||
"chat_ids": [
|
||||
"123456789"
|
||||
],
|
||||
"bot_token": "replace-me",
|
||||
"events": {
|
||||
"task_queued": true,
|
||||
"task_processing": true,
|
||||
"task_completed": true,
|
||||
"task_failed": true,
|
||||
"run_started": true,
|
||||
"implementer_completed": true,
|
||||
"reviewers_started": true,
|
||||
"fix_pass_started": true,
|
||||
"run_heartbeat": true,
|
||||
"run_completed": true,
|
||||
"run_failed": true,
|
||||
"codex_failed": true,
|
||||
"daemon_started": false,
|
||||
"daemon_stopped": false
|
||||
}
|
||||
}
|
||||
47
ralph/gui/README.md
Normal file
47
ralph/gui/README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Ralph GUI
|
||||
|
||||
This is a minimal localhost dashboard for Ralph.
|
||||
|
||||
It does not execute runs by itself.
|
||||
|
||||
It only reads:
|
||||
|
||||
- `ralph/state/current_run.json`
|
||||
- `ralph/state/last_background_run.json`
|
||||
- `ralph/state/events.jsonl`
|
||||
- `ralph/state/provider_smoke.json`
|
||||
- recent folders under `ralph/runs/`
|
||||
|
||||
## Start
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Start-RalphDashboard.ps1
|
||||
```
|
||||
|
||||
Default URL:
|
||||
|
||||
```text
|
||||
http://127.0.0.1:8765
|
||||
```
|
||||
|
||||
## What it shows
|
||||
|
||||
- current run id, stage and status
|
||||
- implementer state
|
||||
- reviewer state
|
||||
- Codex master state
|
||||
- fix-pass state
|
||||
- recent event timeline
|
||||
- latest run folders and summary excerpts
|
||||
- last background runner metadata
|
||||
|
||||
## Note
|
||||
|
||||
The dashboard is intentionally simple.
|
||||
|
||||
It is meant to answer:
|
||||
|
||||
- who is working right now
|
||||
- what stage the swarm is in
|
||||
- whether Codex review already happened
|
||||
- whether a run completed, failed or stopped
|
||||
491
ralph/gui/app.py
Normal file
491
ralph/gui/app.py
Normal file
@@ -0,0 +1,491 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Minimal Ralph dashboard for localhost monitoring."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
from http import HTTPStatus
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
||||
RALPH_ROOT = Path(__file__).resolve().parents[1]
|
||||
STATE_DIR = RALPH_ROOT / "state"
|
||||
RUNS_DIR = RALPH_ROOT / "runs"
|
||||
|
||||
|
||||
def _read_json(path: Path, default: Any) -> Any:
|
||||
try:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
|
||||
def _read_text(path: Path) -> str:
|
||||
try:
|
||||
return path.read_text(encoding="utf-8")
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
def _tail_jsonl(path: Path, limit: int = 80) -> List[Dict[str, Any]]:
|
||||
if not path.exists():
|
||||
return []
|
||||
|
||||
try:
|
||||
lines = path.read_text(encoding="utf-8").splitlines()
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
events: List[Dict[str, Any]] = []
|
||||
for line in lines[-limit:]:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
events.append(json.loads(line))
|
||||
except Exception:
|
||||
continue
|
||||
events.reverse()
|
||||
return events
|
||||
|
||||
|
||||
def _is_pid_running(pid: Any) -> bool:
|
||||
try:
|
||||
pid_value = int(pid)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
if pid_value <= 0:
|
||||
return False
|
||||
|
||||
try:
|
||||
os.kill(pid_value, 0)
|
||||
except PermissionError:
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _iso_to_local(value: Any) -> str:
|
||||
if not value:
|
||||
return ""
|
||||
try:
|
||||
return datetime.fromisoformat(str(value).replace("Z", "+00:00")).strftime("%Y-%m-%d %H:%M:%S")
|
||||
except Exception:
|
||||
return str(value)
|
||||
|
||||
|
||||
def _collect_recent_runs(limit: int = 8) -> List[Dict[str, Any]]:
|
||||
runs: List[Dict[str, Any]] = []
|
||||
if not RUNS_DIR.exists():
|
||||
return runs
|
||||
|
||||
for run_dir in sorted([p for p in RUNS_DIR.iterdir() if p.is_dir()], key=lambda item: item.name, reverse=True)[:limit]:
|
||||
summary_path = run_dir / "SUMMARY.md"
|
||||
final_status_path = run_dir / "final_status.txt"
|
||||
summary_text = _read_text(summary_path).strip()
|
||||
final_status_text = _read_text(final_status_path).strip()
|
||||
changes_exists = (run_dir / "CHANGES.md").exists()
|
||||
implementer_patch = (run_dir / "implementer.patch").exists()
|
||||
final_patch = (run_dir / "final.patch").exists()
|
||||
runs.append(
|
||||
{
|
||||
"run_id": run_dir.name,
|
||||
"path": str(run_dir),
|
||||
"updated_at": datetime.fromtimestamp(run_dir.stat().st_mtime).strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"summary_excerpt": "\n".join(summary_text.splitlines()[:10]),
|
||||
"final_status_excerpt": "\n".join(final_status_text.splitlines()[:8]),
|
||||
"changes_exists": changes_exists,
|
||||
"implementer_patch": implementer_patch,
|
||||
"final_patch": final_patch,
|
||||
}
|
||||
)
|
||||
return runs
|
||||
|
||||
|
||||
def build_dashboard() -> Dict[str, Any]:
|
||||
current_run = _read_json(STATE_DIR / "current_run.json", {})
|
||||
background = _read_json(STATE_DIR / "last_background_run.json", {})
|
||||
background["alive"] = _is_pid_running(background.get("pid"))
|
||||
|
||||
return {
|
||||
"generated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"current_run": current_run,
|
||||
"background": background,
|
||||
"events": _tail_jsonl(STATE_DIR / "events.jsonl", limit=120),
|
||||
"recent_runs": _collect_recent_runs(),
|
||||
"provider_smoke": _read_json(STATE_DIR / "provider_smoke.json", {}),
|
||||
}
|
||||
|
||||
|
||||
HTML_TEMPLATE = """<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Ralph Dashboard</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f3efe5;
|
||||
--ink: #1a1a1a;
|
||||
--muted: #5f5a52;
|
||||
--card: #fffaf2;
|
||||
--line: #d8cfbf;
|
||||
--ok: #2a7f62;
|
||||
--warn: #a96814;
|
||||
--bad: #ad2e24;
|
||||
--run: #144c7d;
|
||||
--shadow: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Segoe UI", "Helvetica Neue", Arial, sans-serif;
|
||||
background: linear-gradient(180deg, #efe7d8 0%, var(--bg) 100%);
|
||||
color: var(--ink);
|
||||
}
|
||||
header {
|
||||
padding: 20px 24px;
|
||||
background: #17324d;
|
||||
color: #fff8eb;
|
||||
border-bottom: 4px solid #c98b2b;
|
||||
}
|
||||
header h1 { margin: 0 0 6px; font-size: 28px; }
|
||||
header p { margin: 0; color: #d7e4ef; }
|
||||
main { padding: 20px 24px 32px; }
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.card {
|
||||
background: var(--card);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 14px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 8px 24px var(--shadow);
|
||||
}
|
||||
.card h2 {
|
||||
margin: 0 0 10px;
|
||||
font-size: 18px;
|
||||
}
|
||||
.muted { color: var(--muted); }
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 4px 9px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
background: #ece4d6;
|
||||
color: var(--ink);
|
||||
}
|
||||
.status-completed, .status-success, .status-running { color: var(--ok); }
|
||||
.status-failed, .status-error { color: var(--bad); }
|
||||
.status-pending, .status-warning { color: var(--warn); }
|
||||
.agents { display: grid; gap: 10px; }
|
||||
.agent {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
padding: 10px 12px;
|
||||
background: #fffdf8;
|
||||
}
|
||||
.agent strong { display: block; margin-bottom: 4px; }
|
||||
.timeline {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
max-height: 540px;
|
||||
overflow: auto;
|
||||
}
|
||||
.timeline li {
|
||||
border-left: 4px solid #c98b2b;
|
||||
padding: 8px 10px 8px 12px;
|
||||
background: #fffdf8;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--line);
|
||||
}
|
||||
.run-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
.run-item {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
padding: 12px;
|
||||
background: #fffdf8;
|
||||
}
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
background: #f6f1e8;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
margin: 8px 0 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
.empty {
|
||||
padding: 16px;
|
||||
border: 1px dashed var(--line);
|
||||
border-radius: 10px;
|
||||
color: var(--muted);
|
||||
background: #faf6ef;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Ralph Dashboard</h1>
|
||||
<p>Simple local view of who is working, what stage the swarm is in, and what happened last.</p>
|
||||
</header>
|
||||
<main>
|
||||
<div class="grid">
|
||||
<section class="card">
|
||||
<h2>Overview</h2>
|
||||
<div id="overview" class="muted">Loading...</div>
|
||||
</section>
|
||||
<section class="card">
|
||||
<h2>Background Runner</h2>
|
||||
<div id="background" class="muted">Loading...</div>
|
||||
</section>
|
||||
<section class="card">
|
||||
<h2>Provider Smoke</h2>
|
||||
<div id="providers" class="muted">Loading...</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<section class="card">
|
||||
<h2>Current Run</h2>
|
||||
<div id="current-run" class="muted">Loading...</div>
|
||||
</section>
|
||||
<section class="card">
|
||||
<h2>Agent Status</h2>
|
||||
<div id="agents" class="agents"></div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<section class="card">
|
||||
<h2>Timeline</h2>
|
||||
<ul id="timeline" class="timeline"></ul>
|
||||
</section>
|
||||
<section class="card">
|
||||
<h2>Recent Runs</h2>
|
||||
<div id="recent-runs" class="run-list"></div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<script>
|
||||
function statusBadge(value) {
|
||||
const text = value || "unknown";
|
||||
const cls = "badge status-" + String(text).toLowerCase();
|
||||
return `<span class="${cls}">${text}</span>`;
|
||||
}
|
||||
|
||||
function renderAgent(name, data) {
|
||||
if (!data) return "";
|
||||
const started = data.started_at ? new Date(data.started_at).toLocaleString() : "";
|
||||
const finished = data.finished_at ? new Date(data.finished_at).toLocaleString() : "";
|
||||
const output = data.output_file ? `<div class="muted">${data.output_file}</div>` : "";
|
||||
return `
|
||||
<div class="agent">
|
||||
<strong>${name}</strong>
|
||||
<div>${statusBadge(data.status)}</div>
|
||||
${started ? `<div class="muted">Start: ${started}</div>` : ""}
|
||||
${finished ? `<div class="muted">End: ${finished}</div>` : ""}
|
||||
${output}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderOverview(data) {
|
||||
const run = data.current_run || {};
|
||||
if (!run.run_id) {
|
||||
return `<div class="empty">No current run state has been written yet.</div>`;
|
||||
}
|
||||
return `
|
||||
<div><strong>Run ID:</strong> ${run.run_id}</div>
|
||||
<div><strong>Stage:</strong> ${statusBadge(run.stage)}</div>
|
||||
<div><strong>Status:</strong> ${statusBadge(run.status)}</div>
|
||||
<div><strong>Message:</strong> ${run.latest_message || "-"}</div>
|
||||
<div class="muted">Updated: ${data.generated_at}</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderBackground(data) {
|
||||
const bg = data.background || {};
|
||||
if (!bg.pid) {
|
||||
return `<div class="empty">No background launch metadata found.</div>`;
|
||||
}
|
||||
return `
|
||||
<div><strong>PID:</strong> ${bg.pid}</div>
|
||||
<div><strong>Alive:</strong> ${statusBadge(bg.alive ? "running" : "stopped")}</div>
|
||||
<div><strong>Run label:</strong> ${bg.run_label || "-"}</div>
|
||||
<div class="muted">${bg.stdout_log || ""}</div>
|
||||
<div class="muted">${bg.stderr_log || ""}</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderProviders(data) {
|
||||
const smoke = data.provider_smoke || {};
|
||||
const checks = smoke.checks || [];
|
||||
if (!checks.length) {
|
||||
return `<div class="empty">No provider smoke data found yet.</div>`;
|
||||
}
|
||||
return checks.map((item) => `
|
||||
<div class="agent">
|
||||
<strong>${item.provider || "provider"}</strong>
|
||||
<div>${statusBadge(item.status || "unknown")}</div>
|
||||
<div class="muted">${item.model || ""}</div>
|
||||
</div>
|
||||
`).join("");
|
||||
}
|
||||
|
||||
function renderCurrentRun(data) {
|
||||
const run = data.current_run || {};
|
||||
if (!run.run_id) {
|
||||
return `<div class="empty">No active or recent run state.</div>`;
|
||||
}
|
||||
return `
|
||||
<div><strong>Run:</strong> ${run.run_id}</div>
|
||||
<div><strong>Task dir:</strong> ${run.task_directory || "-"}</div>
|
||||
<div><strong>Worktree:</strong> ${run.worktree || "-"}</div>
|
||||
<div><strong>Started:</strong> ${run.started_at ? new Date(run.started_at).toLocaleString() : "-"}</div>
|
||||
<div><strong>Finished:</strong> ${run.finished_at ? new Date(run.finished_at).toLocaleString() : "-"}</div>
|
||||
${(run.errors || []).length ? `<pre>${(run.errors || []).join("\\n")}</pre>` : ""}
|
||||
`;
|
||||
}
|
||||
|
||||
function renderAgents(data) {
|
||||
const run = data.current_run || {};
|
||||
const html = [];
|
||||
if (run.implementer) {
|
||||
html.push(renderAgent("Implementer: " + (run.implementer.name || "unknown"), run.implementer));
|
||||
}
|
||||
(run.reviewers || []).forEach((reviewer) => {
|
||||
html.push(renderAgent("Reviewer: " + (reviewer.name || "unknown"), reviewer));
|
||||
});
|
||||
if (run.codex_master) {
|
||||
html.push(renderAgent("Codex Master", run.codex_master));
|
||||
}
|
||||
if (run.fix_pass) {
|
||||
html.push(renderAgent("Fix Pass", run.fix_pass));
|
||||
}
|
||||
document.getElementById("agents").innerHTML = html.length ? html.join("") : `<div class="empty">No agent state yet.</div>`;
|
||||
}
|
||||
|
||||
function renderTimeline(data) {
|
||||
const events = data.events || [];
|
||||
const target = document.getElementById("timeline");
|
||||
if (!events.length) {
|
||||
target.innerHTML = `<li class="empty">No events yet.</li>`;
|
||||
return;
|
||||
}
|
||||
target.innerHTML = events.map((event) => `
|
||||
<li>
|
||||
<div><strong>${event.actor || "system"}</strong> ${statusBadge(event.status || "unknown")}</div>
|
||||
<div>${event.message || "-"}</div>
|
||||
<div class="muted">${event.stage || "-"} · ${new Date(event.timestamp).toLocaleString()}</div>
|
||||
</li>
|
||||
`).join("");
|
||||
}
|
||||
|
||||
function renderRecentRuns(data) {
|
||||
const runs = data.recent_runs || [];
|
||||
const target = document.getElementById("recent-runs");
|
||||
if (!runs.length) {
|
||||
target.innerHTML = `<div class="empty">No run folders found.</div>`;
|
||||
return;
|
||||
}
|
||||
target.innerHTML = runs.map((run) => `
|
||||
<div class="run-item">
|
||||
<div><strong>${run.run_id}</strong></div>
|
||||
<div class="muted">${run.updated_at}</div>
|
||||
<div>${run.changes_exists ? statusBadge("changes") : statusBadge("no changes")}</div>
|
||||
<div>${run.final_patch ? statusBadge("final patch") : statusBadge("no final patch")}</div>
|
||||
${run.final_status_excerpt ? `<pre>${run.final_status_excerpt}</pre>` : ""}
|
||||
${run.summary_excerpt ? `<pre>${run.summary_excerpt}</pre>` : ""}
|
||||
</div>
|
||||
`).join("");
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
const response = await fetch("/api/dashboard", { cache: "no-store" });
|
||||
const data = await response.json();
|
||||
document.getElementById("overview").innerHTML = renderOverview(data);
|
||||
document.getElementById("background").innerHTML = renderBackground(data);
|
||||
document.getElementById("providers").innerHTML = renderProviders(data);
|
||||
document.getElementById("current-run").innerHTML = renderCurrentRun(data);
|
||||
renderAgents(data);
|
||||
renderTimeline(data);
|
||||
renderRecentRuns(data);
|
||||
}
|
||||
|
||||
refresh();
|
||||
setInterval(refresh, 2000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
class RalphHandler(BaseHTTPRequestHandler):
|
||||
def _send_json(self, payload: Dict[str, Any]) -> None:
|
||||
body = json.dumps(payload, indent=2).encode("utf-8")
|
||||
self.send_response(HTTPStatus.OK)
|
||||
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||||
self.send_header("Cache-Control", "no-store")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
def _send_html(self, payload: str) -> None:
|
||||
body = payload.encode("utf-8")
|
||||
self.send_response(HTTPStatus.OK)
|
||||
self.send_header("Content-Type", "text/html; charset=utf-8")
|
||||
self.send_header("Cache-Control", "no-store")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
def do_GET(self) -> None:
|
||||
route = urlparse(self.path).path
|
||||
if route == "/api/dashboard":
|
||||
self._send_json(build_dashboard())
|
||||
return
|
||||
if route == "/" or route == "/index.html":
|
||||
self._send_html(HTML_TEMPLATE)
|
||||
return
|
||||
|
||||
self.send_error(HTTPStatus.NOT_FOUND, "Not found")
|
||||
|
||||
def log_message(self, fmt: str, *args: Any) -> None:
|
||||
return
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Run the Ralph localhost dashboard.")
|
||||
parser.add_argument("--host", default="127.0.0.1", help="Bind host (default: 127.0.0.1)")
|
||||
parser.add_argument("--port", type=int, default=8765, help="Bind port (default: 8765)")
|
||||
args = parser.parse_args()
|
||||
|
||||
server = ThreadingHTTPServer((args.host, args.port), RalphHandler)
|
||||
print(f"Ralph dashboard listening on http://{args.host}:{args.port}")
|
||||
server.serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1
ralph/logs/.gitkeep
Normal file
1
ralph/logs/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
ralph/runs/.gitkeep
Normal file
1
ralph/runs/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
664
ralph/scripts/Common.ps1
Normal file
664
ralph/scripts/Common.ps1
Normal file
@@ -0,0 +1,664 @@
|
||||
Set-StrictMode -Version 3.0
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Get-RalphRoot {
|
||||
return (Split-Path -Parent $PSScriptRoot)
|
||||
}
|
||||
|
||||
function Get-RepoRoot {
|
||||
return (Split-Path -Parent (Get-RalphRoot))
|
||||
}
|
||||
|
||||
function Ensure-Directory {
|
||||
param([Parameter(Mandatory = $true)][string]$Path)
|
||||
if (-not (Test-Path $Path)) {
|
||||
New-Item -ItemType Directory -Force -Path $Path | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
function Read-JsonFile {
|
||||
param([Parameter(Mandatory = $true)][string]$Path)
|
||||
if (-not (Test-Path $Path)) {
|
||||
throw "JSON file not found: $Path"
|
||||
}
|
||||
|
||||
return Get-Content -Raw -Path $Path | ConvertFrom-Json
|
||||
}
|
||||
|
||||
function Get-ProvidersConfig {
|
||||
$ralphRoot = Get-RalphRoot
|
||||
$localPath = Join-Path $ralphRoot "config\providers.local.json"
|
||||
$examplePath = Join-Path $ralphRoot "config\providers.example.json"
|
||||
|
||||
if (Test-Path $localPath) {
|
||||
return Read-JsonFile -Path $localPath
|
||||
}
|
||||
|
||||
return Read-JsonFile -Path $examplePath
|
||||
}
|
||||
|
||||
function Get-ProviderConfig {
|
||||
param([Parameter(Mandatory = $true)][string]$Name)
|
||||
|
||||
$config = Get-ProvidersConfig
|
||||
if (-not $config.providers.PSObject.Properties.Name.Contains($Name)) {
|
||||
throw "Provider '$Name' not found in providers config."
|
||||
}
|
||||
|
||||
return $config.providers.$Name
|
||||
}
|
||||
|
||||
function Get-DefaultImplementer {
|
||||
$config = Get-ProvidersConfig
|
||||
return [string]$config.default_implementer
|
||||
}
|
||||
|
||||
function Get-DefaultReviewers {
|
||||
$config = Get-ProvidersConfig
|
||||
return @($config.default_reviewers)
|
||||
}
|
||||
|
||||
function Get-CodexConfig {
|
||||
$ralphRoot = Get-RalphRoot
|
||||
$localPath = Join-Path $ralphRoot "config\codex.local.json"
|
||||
$examplePath = Join-Path $ralphRoot "config\codex.example.json"
|
||||
|
||||
if (Test-Path $localPath) {
|
||||
return Read-JsonFile -Path $localPath
|
||||
}
|
||||
|
||||
return Read-JsonFile -Path $examplePath
|
||||
}
|
||||
|
||||
function Get-RalphAutomationConfig {
|
||||
$ralphRoot = Get-RalphRoot
|
||||
$localPath = Join-Path $ralphRoot "config\automation.local.json"
|
||||
$examplePath = Join-Path $ralphRoot "config\automation.example.json"
|
||||
|
||||
if (Test-Path $localPath) {
|
||||
return Read-JsonFile -Path $localPath
|
||||
}
|
||||
|
||||
if (Test-Path $examplePath) {
|
||||
return Read-JsonFile -Path $examplePath
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-TelegramConfig {
|
||||
$ralphRoot = Get-RalphRoot
|
||||
$localPath = Join-Path $ralphRoot "config\telegram.local.json"
|
||||
$examplePath = Join-Path $ralphRoot "config\telegram.example.json"
|
||||
|
||||
if (Test-Path $localPath) {
|
||||
return Read-JsonFile -Path $localPath
|
||||
}
|
||||
|
||||
if (Test-Path $examplePath) {
|
||||
return Read-JsonFile -Path $examplePath
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-TelegramChatIds {
|
||||
param($Config)
|
||||
|
||||
if ($null -eq $Config) {
|
||||
return @()
|
||||
}
|
||||
|
||||
if ($Config.PSObject.Properties.Name -contains "chat_ids") {
|
||||
return @($Config.chat_ids | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) } | ForEach-Object { [string]$_ })
|
||||
}
|
||||
|
||||
if ($Config.PSObject.Properties.Name -contains "chat_id" -and -not [string]::IsNullOrWhiteSpace([string]$Config.chat_id)) {
|
||||
return @([string]$Config.chat_id)
|
||||
}
|
||||
|
||||
return @()
|
||||
}
|
||||
|
||||
function Test-TelegramEventEnabled {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]$Config,
|
||||
[Parameter(Mandatory = $true)][string]$EventName
|
||||
)
|
||||
|
||||
$enabled = $false
|
||||
try { $enabled = [bool]$Config.enabled } catch { $enabled = $false }
|
||||
if (-not $enabled) {
|
||||
return $false
|
||||
}
|
||||
|
||||
if ($Config.PSObject.Properties.Name -contains "events" -and $null -ne $Config.events) {
|
||||
$eventProperty = $Config.events.PSObject.Properties[$EventName]
|
||||
if ($null -ne $eventProperty) {
|
||||
try { return [bool]$eventProperty.Value } catch { return $false }
|
||||
}
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
function Send-TelegramNotification {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$EventName,
|
||||
[Parameter(Mandatory = $true)][string]$Title,
|
||||
[Parameter(Mandatory = $true)][string]$Message,
|
||||
[string]$RunId = "",
|
||||
[string]$Stage = "",
|
||||
[string]$Status = ""
|
||||
)
|
||||
|
||||
try {
|
||||
$config = Get-TelegramConfig
|
||||
}
|
||||
catch {
|
||||
return [ordered]@{
|
||||
sent = $false
|
||||
reason = $_.Exception.Message
|
||||
delivered = 0
|
||||
errors = @()
|
||||
}
|
||||
}
|
||||
|
||||
if ($null -eq $config) {
|
||||
return [ordered]@{
|
||||
sent = $false
|
||||
reason = "telegram config missing"
|
||||
delivered = 0
|
||||
errors = @()
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Test-TelegramEventEnabled -Config $config -EventName $EventName)) {
|
||||
return [ordered]@{
|
||||
sent = $false
|
||||
reason = "event disabled"
|
||||
delivered = 0
|
||||
errors = @()
|
||||
}
|
||||
}
|
||||
|
||||
$botToken = ""
|
||||
if ($config.PSObject.Properties.Name -contains "bot_token") {
|
||||
$botToken = [string]$config.bot_token
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($botToken)) {
|
||||
return [ordered]@{
|
||||
sent = $false
|
||||
reason = "bot token missing"
|
||||
delivered = 0
|
||||
errors = @()
|
||||
}
|
||||
}
|
||||
|
||||
$chatIds = @(Get-TelegramChatIds -Config $config)
|
||||
if ($chatIds.Count -eq 0) {
|
||||
return [ordered]@{
|
||||
sent = $false
|
||||
reason = "chat ids missing"
|
||||
delivered = 0
|
||||
errors = @()
|
||||
}
|
||||
}
|
||||
|
||||
$timeoutSeconds = 8
|
||||
if ($config.PSObject.Properties.Name -contains "timeout_seconds") {
|
||||
try {
|
||||
$timeoutSeconds = [int]$config.timeout_seconds
|
||||
}
|
||||
catch {
|
||||
$timeoutSeconds = 8
|
||||
}
|
||||
}
|
||||
|
||||
$prefix = "Ralph"
|
||||
if ($config.PSObject.Properties.Name -contains "prefix" -and -not [string]::IsNullOrWhiteSpace([string]$config.prefix)) {
|
||||
$prefix = [string]$config.prefix
|
||||
}
|
||||
|
||||
$lines = @(
|
||||
("{0} | {1}" -f $prefix, $Title.Trim())
|
||||
$Message.Trim()
|
||||
)
|
||||
if (-not [string]::IsNullOrWhiteSpace($RunId)) {
|
||||
$lines += ("run: " + $RunId)
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($Stage) -or -not [string]::IsNullOrWhiteSpace($Status)) {
|
||||
$statusLine = @()
|
||||
if (-not [string]::IsNullOrWhiteSpace($Stage)) {
|
||||
$statusLine += ("stage=" + $Stage)
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($Status)) {
|
||||
$statusLine += ("status=" + $Status)
|
||||
}
|
||||
if ($statusLine.Count -gt 0) {
|
||||
$lines += ($statusLine -join " ")
|
||||
}
|
||||
}
|
||||
$text = ($lines -join "`n").Trim()
|
||||
|
||||
$uri = "https://api.telegram.org/bot{0}/sendMessage" -f $botToken
|
||||
$delivered = 0
|
||||
$errors = New-Object System.Collections.Generic.List[string]
|
||||
|
||||
foreach ($chatId in $chatIds) {
|
||||
try {
|
||||
$body = @{
|
||||
chat_id = $chatId
|
||||
text = $text
|
||||
disable_web_page_preview = $true
|
||||
}
|
||||
Invoke-RestMethod -Method Post -Uri $uri -Body $body -ContentType "application/x-www-form-urlencoded" -TimeoutSec $timeoutSeconds | Out-Null
|
||||
$delivered += 1
|
||||
}
|
||||
catch {
|
||||
$errors.Add(("{0}: {1}" -f $chatId, $_.Exception.Message))
|
||||
}
|
||||
}
|
||||
|
||||
return [ordered]@{
|
||||
sent = ($delivered -gt 0)
|
||||
reason = $(if ($delivered -gt 0) { "ok" } else { "delivery failed" })
|
||||
delivered = $delivered
|
||||
errors = @($errors)
|
||||
}
|
||||
}
|
||||
|
||||
function New-RunId {
|
||||
param([string]$Label = "autopilot")
|
||||
return "{0}-{1}" -f (Get-Date -Format "yyyyMMdd-HHmmss"), $Label
|
||||
}
|
||||
|
||||
function Get-TaskFiles {
|
||||
param([string]$TaskDirectory = $(Join-Path (Get-RalphRoot) "tasks\current"))
|
||||
|
||||
return @{
|
||||
Task = Join-Path $TaskDirectory "TASK.md"
|
||||
Acceptance = Join-Path $TaskDirectory "ACCEPTANCE.md"
|
||||
Context = Join-Path $TaskDirectory "CONTEXT.md"
|
||||
}
|
||||
}
|
||||
|
||||
function Get-RalphInboxDirectory {
|
||||
return (Join-Path (Get-RalphRoot) "tasks\inbox")
|
||||
}
|
||||
|
||||
function Get-RalphProcessingDirectory {
|
||||
return (Join-Path (Get-RalphRoot) "tasks\processing")
|
||||
}
|
||||
|
||||
function Get-RalphArchiveDirectory {
|
||||
return (Join-Path (Get-RalphRoot) "tasks\completed")
|
||||
}
|
||||
|
||||
function Get-RalphFailedDirectory {
|
||||
return (Join-Path (Get-RalphRoot) "tasks\failed")
|
||||
}
|
||||
|
||||
function Read-TaskPack {
|
||||
param([string]$TaskDirectory = $(Join-Path (Get-RalphRoot) "tasks\current"))
|
||||
|
||||
$files = Get-TaskFiles -TaskDirectory $TaskDirectory
|
||||
foreach ($entry in $files.GetEnumerator()) {
|
||||
if (-not (Test-Path $entry.Value)) {
|
||||
throw "Task pack file missing: $($entry.Value)"
|
||||
}
|
||||
}
|
||||
|
||||
return @{
|
||||
Files = $files
|
||||
Task = Get-Content -Raw -Path $files.Task
|
||||
Acceptance = Get-Content -Raw -Path $files.Acceptance
|
||||
Context = Get-Content -Raw -Path $files.Context
|
||||
}
|
||||
}
|
||||
|
||||
function Convert-MarkdownToRalphTaskPack {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Path
|
||||
)
|
||||
|
||||
if (-not (Test-Path $Path)) {
|
||||
throw "Task markdown not found: $Path"
|
||||
}
|
||||
|
||||
$raw = Get-Content -Raw -Path $Path
|
||||
$lines = $raw -split "`r?`n"
|
||||
$taskLines = New-Object System.Collections.Generic.List[string]
|
||||
$acceptanceLines = New-Object System.Collections.Generic.List[string]
|
||||
$contextLines = New-Object System.Collections.Generic.List[string]
|
||||
$current = "task"
|
||||
$title = ""
|
||||
|
||||
foreach ($line in $lines) {
|
||||
if ($line -match '^\s*#{1,3}\s+(.*\S)\s*$') {
|
||||
$heading = $matches[1].Trim()
|
||||
if (-not $title -and $line -match '^\s*#\s+') {
|
||||
$title = $heading
|
||||
}
|
||||
|
||||
$headingLower = $heading.ToLowerInvariant()
|
||||
if ($headingLower -match '^(acceptance|acceptance criteria|criteria|done|definition of done)\b') {
|
||||
$current = "acceptance"
|
||||
}
|
||||
elseif ($headingLower -match '^(context|background|notes|references)\b') {
|
||||
$current = "context"
|
||||
}
|
||||
else {
|
||||
$current = "task"
|
||||
$taskLines.Add($line)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch ($current) {
|
||||
"acceptance" { $acceptanceLines.Add($line) }
|
||||
"context" { $contextLines.Add($line) }
|
||||
default { $taskLines.Add($line) }
|
||||
}
|
||||
}
|
||||
|
||||
$taskText = (($taskLines -join "`n").Trim())
|
||||
$acceptanceText = (($acceptanceLines -join "`n").Trim())
|
||||
$contextText = (($contextLines -join "`n").Trim())
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($taskText)) {
|
||||
$taskText = $raw.Trim()
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($acceptanceText)) {
|
||||
$acceptanceText = Get-Content -Raw -Path (Join-Path (Get-RalphRoot) "templates\ACCEPTANCE.md")
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($contextText)) {
|
||||
$contextText = Get-Content -Raw -Path (Join-Path (Get-RalphRoot) "templates\CONTEXT.md")
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($title)) {
|
||||
$title = [IO.Path]::GetFileNameWithoutExtension($Path)
|
||||
}
|
||||
|
||||
return [ordered]@{
|
||||
Title = $title
|
||||
SourcePath = $Path
|
||||
RawContent = $raw
|
||||
Task = $taskText
|
||||
Acceptance = $acceptanceText
|
||||
Context = $contextText
|
||||
}
|
||||
}
|
||||
|
||||
function Write-RalphTaskPackDirectory {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][hashtable]$TaskPack,
|
||||
[Parameter(Mandatory = $true)][string]$DestinationDirectory
|
||||
)
|
||||
|
||||
Ensure-Directory -Path $DestinationDirectory
|
||||
Write-Utf8File -Path (Join-Path $DestinationDirectory "TASK.md") -Content (($TaskPack.Task.Trim()) + "`n")
|
||||
Write-Utf8File -Path (Join-Path $DestinationDirectory "ACCEPTANCE.md") -Content (($TaskPack.Acceptance.Trim()) + "`n")
|
||||
Write-Utf8File -Path (Join-Path $DestinationDirectory "CONTEXT.md") -Content (($TaskPack.Context.Trim()) + "`n")
|
||||
|
||||
if ($TaskPack.ContainsKey("SourcePath") -and (Test-Path $TaskPack.SourcePath)) {
|
||||
Copy-Item -Path $TaskPack.SourcePath -Destination (Join-Path $DestinationDirectory "SOURCE.md") -Force
|
||||
}
|
||||
}
|
||||
|
||||
function Write-Utf8File {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Path,
|
||||
[Parameter(Mandatory = $true)][AllowEmptyString()][string]$Content
|
||||
)
|
||||
|
||||
$encoding = New-Object System.Text.UTF8Encoding($false)
|
||||
[System.IO.File]::WriteAllText($Path, $Content, $encoding)
|
||||
}
|
||||
|
||||
function Write-JsonFile {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Path,
|
||||
[Parameter(Mandatory = $true)]$Object
|
||||
)
|
||||
|
||||
$json = $Object | ConvertTo-Json -Depth 100
|
||||
Write-Utf8File -Path $Path -Content ($json + "`n")
|
||||
}
|
||||
|
||||
function Get-RalphStateFile {
|
||||
param([Parameter(Mandatory = $true)][string]$Name)
|
||||
return Join-Path (Get-RalphRoot) ("state\" + $Name)
|
||||
}
|
||||
|
||||
function Get-RalphTaskRoots {
|
||||
$ralphRoot = Get-RalphRoot
|
||||
return [ordered]@{
|
||||
TasksRoot = Join-Path $ralphRoot "tasks"
|
||||
Inbox = Join-Path $ralphRoot "tasks\inbox"
|
||||
Processing = Join-Path $ralphRoot "tasks\processing"
|
||||
Completed = Join-Path $ralphRoot "tasks\completed"
|
||||
Failed = Join-Path $ralphRoot "tasks\failed"
|
||||
}
|
||||
}
|
||||
|
||||
function Convert-ToRalphSlug {
|
||||
param([Parameter(Mandatory = $true)][string]$Text)
|
||||
|
||||
$slug = [string]$Text
|
||||
$slug = $slug.Trim().ToLowerInvariant()
|
||||
if ([string]::IsNullOrWhiteSpace($slug)) {
|
||||
return "task"
|
||||
}
|
||||
|
||||
$slug = [regex]::Replace($slug, "[^a-z0-9]+", "-")
|
||||
$slug = $slug.Trim("-")
|
||||
if ([string]::IsNullOrWhiteSpace($slug)) {
|
||||
return "task"
|
||||
}
|
||||
if ($slug.Length -gt 48) {
|
||||
$slug = $slug.Substring(0, 48).Trim("-")
|
||||
}
|
||||
return $slug
|
||||
}
|
||||
|
||||
function Get-RalphTaskTitleFromMarkdown {
|
||||
param(
|
||||
[string]$Markdown = "",
|
||||
[string]$Fallback = "task"
|
||||
)
|
||||
|
||||
$lines = @($Markdown -split "`r?`n")
|
||||
foreach ($line in $lines) {
|
||||
$trimmed = [string]$line
|
||||
$trimmed = $trimmed.Trim()
|
||||
if ([string]::IsNullOrWhiteSpace($trimmed)) {
|
||||
continue
|
||||
}
|
||||
if ($trimmed.StartsWith("#")) {
|
||||
return ($trimmed.TrimStart("#").Trim())
|
||||
}
|
||||
return $trimmed
|
||||
}
|
||||
return $Fallback
|
||||
}
|
||||
|
||||
function New-RalphTaskPackFromMarkdown {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$MarkdownPath,
|
||||
[string]$TargetDirectory = "",
|
||||
[string]$TaskId = "",
|
||||
[string]$Title = "",
|
||||
[hashtable]$AdditionalMetadata = @{}
|
||||
)
|
||||
|
||||
if (-not (Test-Path $MarkdownPath)) {
|
||||
throw "Markdown source not found: $MarkdownPath"
|
||||
}
|
||||
|
||||
$roots = Get-RalphTaskRoots
|
||||
foreach ($path in $roots.Values) {
|
||||
Ensure-Directory -Path $path
|
||||
}
|
||||
|
||||
$parsedTaskPack = Convert-MarkdownToRalphTaskPack -Path $MarkdownPath
|
||||
$sourceText = [string]$parsedTaskPack.RawContent
|
||||
$sourceName = [System.IO.Path]::GetFileNameWithoutExtension($MarkdownPath)
|
||||
if ([string]::IsNullOrWhiteSpace($Title)) {
|
||||
$Title = [string]$parsedTaskPack.Title
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($TaskId)) {
|
||||
$TaskId = "{0}-{1}" -f (Get-Date -Format "yyyyMMdd-HHmmss"), (Convert-ToRalphSlug -Text $Title)
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($TargetDirectory)) {
|
||||
$TargetDirectory = Join-Path $roots.Inbox $TaskId
|
||||
}
|
||||
|
||||
Ensure-Directory -Path $TargetDirectory
|
||||
|
||||
$taskPath = Join-Path $TargetDirectory "TASK.md"
|
||||
$acceptancePath = Join-Path $TargetDirectory "ACCEPTANCE.md"
|
||||
$contextPath = Join-Path $TargetDirectory "CONTEXT.md"
|
||||
$metadataPath = Join-Path $TargetDirectory "submission.json"
|
||||
$sourceCopyPath = Join-Path $TargetDirectory "SOURCE.md"
|
||||
|
||||
$acceptance = [string]$parsedTaskPack.Acceptance
|
||||
$context = [string]$parsedTaskPack.Context
|
||||
$taskBody = [string]$parsedTaskPack.Task
|
||||
|
||||
Write-Utf8File -Path $taskPath -Content ($taskBody.Trim() + "`n")
|
||||
Write-Utf8File -Path $sourceCopyPath -Content ($sourceText.Trim() + "`n")
|
||||
Write-Utf8File -Path $acceptancePath -Content ($acceptance + "`n")
|
||||
Write-Utf8File -Path $contextPath -Content ($context + "`n")
|
||||
$metadata = [ordered]@{
|
||||
id = $TaskId
|
||||
title = $Title
|
||||
submitted_at = (Get-Date).ToString("o")
|
||||
source_path = (Resolve-Path $MarkdownPath).Path
|
||||
task_directory = $TargetDirectory
|
||||
state = "queued"
|
||||
}
|
||||
foreach ($key in $AdditionalMetadata.Keys) {
|
||||
$metadata[$key] = $AdditionalMetadata[$key]
|
||||
}
|
||||
Write-JsonFile -Path $metadataPath -Object $metadata
|
||||
|
||||
return [ordered]@{
|
||||
id = $TaskId
|
||||
title = $Title
|
||||
task_directory = $TargetDirectory
|
||||
task_file = $taskPath
|
||||
acceptance_file = $acceptancePath
|
||||
context_file = $contextPath
|
||||
metadata_file = $metadataPath
|
||||
source_copy = $sourceCopyPath
|
||||
}
|
||||
}
|
||||
|
||||
function Read-CodexReviewVerdict {
|
||||
param([Parameter(Mandatory = $true)][string]$Path)
|
||||
|
||||
if (-not (Test-Path $Path)) {
|
||||
throw "Codex review file not found: $Path"
|
||||
}
|
||||
|
||||
$raw = (Get-Content -Raw -Path $Path).Trim()
|
||||
if ([string]::IsNullOrWhiteSpace($raw)) {
|
||||
throw "Codex review file is empty: $Path"
|
||||
}
|
||||
|
||||
$jsonText = $raw
|
||||
if ($raw -match '(?s)```json\s*(\{.*?\})\s*```') {
|
||||
$jsonText = $matches[1]
|
||||
}
|
||||
elseif ($raw -match '(?s)(\{.*\})') {
|
||||
$jsonText = $matches[1]
|
||||
}
|
||||
|
||||
try {
|
||||
$parsed = $jsonText | ConvertFrom-Json
|
||||
}
|
||||
catch {
|
||||
throw "Codex review output is not valid JSON: $Path"
|
||||
}
|
||||
|
||||
$verdict = [string]$parsed.verdict
|
||||
if ([string]::IsNullOrWhiteSpace($verdict)) {
|
||||
throw "Codex review JSON missing 'verdict': $Path"
|
||||
}
|
||||
|
||||
$normalizedVerdict = $verdict.Trim().ToLowerInvariant()
|
||||
if ($normalizedVerdict -notin @("pass", "needs_fix", "fail")) {
|
||||
throw "Codex review verdict '$verdict' is invalid. Expected: pass, needs_fix, fail."
|
||||
}
|
||||
|
||||
$acceptancePassed = $false
|
||||
try {
|
||||
$acceptancePassed = [bool]$parsed.acceptance_passed
|
||||
}
|
||||
catch {
|
||||
$acceptancePassed = ($normalizedVerdict -eq "pass")
|
||||
}
|
||||
|
||||
return [ordered]@{
|
||||
verdict = $normalizedVerdict
|
||||
acceptance_passed = $acceptancePassed
|
||||
fix_required = [bool]$parsed.fix_required
|
||||
summary = [string]$parsed.summary
|
||||
next_sprint_needed = [bool]$parsed.next_sprint_needed
|
||||
next_sprint_brief = [string]$parsed.next_sprint_brief
|
||||
highest_risk_issues = @($parsed.highest_risk_issues)
|
||||
raw = $parsed
|
||||
}
|
||||
}
|
||||
|
||||
function Add-RalphEvent {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$RunId,
|
||||
[Parameter(Mandatory = $true)][string]$Stage,
|
||||
[Parameter(Mandatory = $true)][string]$Status,
|
||||
[Parameter(Mandatory = $true)][string]$Message,
|
||||
[string]$Actor = "system",
|
||||
[hashtable]$Data = @{}
|
||||
)
|
||||
|
||||
$eventsPath = Get-RalphStateFile -Name "events.jsonl"
|
||||
Ensure-Directory -Path (Split-Path -Parent $eventsPath)
|
||||
|
||||
$event = [ordered]@{
|
||||
timestamp = (Get-Date).ToString("o")
|
||||
run_id = $RunId
|
||||
actor = $Actor
|
||||
stage = $Stage
|
||||
status = $Status
|
||||
message = $Message
|
||||
data = $Data
|
||||
}
|
||||
|
||||
$encoding = New-Object System.Text.UTF8Encoding($false)
|
||||
[System.IO.File]::AppendAllText($eventsPath, (($event | ConvertTo-Json -Depth 20 -Compress) + "`n"), $encoding)
|
||||
}
|
||||
|
||||
function Set-RalphCurrentRunState {
|
||||
param([Parameter(Mandatory = $true)][hashtable]$State)
|
||||
$statePath = Get-RalphStateFile -Name "current_run.json"
|
||||
Write-JsonFile -Path $statePath -Object $State
|
||||
}
|
||||
|
||||
function New-PromptDocument {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$TemplatePath,
|
||||
[Parameter(Mandatory = $true)][hashtable]$TaskPack,
|
||||
[Parameter(Mandatory = $true)][string]$OutputPath,
|
||||
[string[]]$ExtraSections = @()
|
||||
)
|
||||
|
||||
$template = Get-Content -Raw -Path $TemplatePath
|
||||
$sections = @(
|
||||
$template,
|
||||
"## TASK`n$($TaskPack.Task)",
|
||||
"## ACCEPTANCE`n$($TaskPack.Acceptance)",
|
||||
"## CONTEXT`n$($TaskPack.Context)"
|
||||
) + $ExtraSections
|
||||
|
||||
Write-Utf8File -Path $OutputPath -Content (($sections -join "`n`n").Trim() + "`n")
|
||||
}
|
||||
63
ralph/scripts/Get-RalphStatus.ps1
Normal file
63
ralph/scripts/Get-RalphStatus.ps1
Normal file
@@ -0,0 +1,63 @@
|
||||
. (Join-Path $PSScriptRoot "Common.ps1")
|
||||
|
||||
$roots = Get-RalphTaskRoots
|
||||
$lockFile = Get-RalphStateFile -Name "inbox_daemon.lock.json"
|
||||
$daemonStateFile = Get-RalphStateFile -Name "inbox_daemon_state.json"
|
||||
$currentRunFile = Get-RalphStateFile -Name "current_run.json"
|
||||
$backgroundFile = Join-Path (Get-RalphRoot) "state\last_inbox_background.json"
|
||||
|
||||
function Get-ItemCount {
|
||||
param([object[]]$Items)
|
||||
return [int](($Items | Measure-Object).Count)
|
||||
}
|
||||
|
||||
$result = [ordered]@{
|
||||
inbox = [ordered]@{
|
||||
queued_taskpacks = Get-ItemCount -Items @(Get-ChildItem -LiteralPath $roots.Inbox -Directory -ErrorAction SilentlyContinue | Where-Object { Test-Path (Join-Path $_.FullName "TASK.md") })
|
||||
queued_markdown_files = Get-ItemCount -Items @(Get-ChildItem -LiteralPath $roots.Inbox -File -Filter *.md -ErrorAction SilentlyContinue)
|
||||
processing = Get-ItemCount -Items @(Get-ChildItem -LiteralPath $roots.Processing -Directory -ErrorAction SilentlyContinue)
|
||||
completed = Get-ItemCount -Items @(Get-ChildItem -LiteralPath $roots.Completed -Directory -ErrorAction SilentlyContinue)
|
||||
failed = Get-ItemCount -Items @(Get-ChildItem -LiteralPath $roots.Failed -Directory -ErrorAction SilentlyContinue)
|
||||
}
|
||||
daemon = $null
|
||||
current_run = $null
|
||||
background = $null
|
||||
}
|
||||
|
||||
if (Test-Path $lockFile) {
|
||||
try {
|
||||
$result.daemon = Read-JsonFile -Path $lockFile
|
||||
}
|
||||
catch {
|
||||
$result.daemon = @{ error = $_.Exception.Message }
|
||||
}
|
||||
}
|
||||
|
||||
if (Test-Path $daemonStateFile) {
|
||||
try {
|
||||
$result.daemon_state = Read-JsonFile -Path $daemonStateFile
|
||||
}
|
||||
catch {
|
||||
$result.daemon_state = @{ error = $_.Exception.Message }
|
||||
}
|
||||
}
|
||||
|
||||
if (Test-Path $currentRunFile) {
|
||||
try {
|
||||
$result.current_run = Read-JsonFile -Path $currentRunFile
|
||||
}
|
||||
catch {
|
||||
$result.current_run = @{ error = $_.Exception.Message }
|
||||
}
|
||||
}
|
||||
|
||||
if (Test-Path $backgroundFile) {
|
||||
try {
|
||||
$result.background = Read-JsonFile -Path $backgroundFile
|
||||
}
|
||||
catch {
|
||||
$result.background = @{ error = $_.Exception.Message }
|
||||
}
|
||||
}
|
||||
|
||||
($result | ConvertTo-Json -Depth 100)
|
||||
62
ralph/scripts/Install-RalphScheduledTask.ps1
Normal file
62
ralph/scripts/Install-RalphScheduledTask.ps1
Normal file
@@ -0,0 +1,62 @@
|
||||
param(
|
||||
[string]$TaskName = "RalphInboxDaemon",
|
||||
[int]$PollSeconds = 15,
|
||||
[string]$Implementer = "",
|
||||
[string[]]$Reviewers = @(),
|
||||
[switch]$DisableCodexMaster,
|
||||
[switch]$DisableAutoFix,
|
||||
[switch]$DryRun,
|
||||
[switch]$AtStartup,
|
||||
[switch]$AtLogon = $true
|
||||
)
|
||||
|
||||
. (Join-Path $PSScriptRoot "Common.ps1")
|
||||
|
||||
$daemonScript = Join-Path $PSScriptRoot "Start-RalphInboxDaemon.ps1"
|
||||
$argParts = @(
|
||||
"-NoProfile",
|
||||
"-WindowStyle Hidden",
|
||||
"-ExecutionPolicy Bypass",
|
||||
('-File "{0}"' -f $daemonScript),
|
||||
('-PollSeconds {0}' -f $PollSeconds)
|
||||
)
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($Implementer)) {
|
||||
$argParts += ('-Implementer "{0}"' -f $Implementer)
|
||||
}
|
||||
foreach ($reviewer in $Reviewers) {
|
||||
$argParts += ('-Reviewers "{0}"' -f $reviewer)
|
||||
}
|
||||
if ($DisableCodexMaster) {
|
||||
$argParts += "-DisableCodexMaster"
|
||||
}
|
||||
if ($DisableAutoFix) {
|
||||
$argParts += "-DisableAutoFix"
|
||||
}
|
||||
if ($DryRun) {
|
||||
$argParts += "-DryRun"
|
||||
}
|
||||
|
||||
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument ($argParts -join " ")
|
||||
$triggers = @()
|
||||
if ($AtStartup) {
|
||||
$triggers += New-ScheduledTaskTrigger -AtStartup
|
||||
}
|
||||
if ($AtLogon) {
|
||||
$triggers += New-ScheduledTaskTrigger -AtLogOn
|
||||
}
|
||||
if ($triggers.Count -eq 0) {
|
||||
throw "At least one trigger must be enabled."
|
||||
}
|
||||
|
||||
$settings = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew -StartWhenAvailable -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
|
||||
$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive -RunLevel Highest
|
||||
|
||||
Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $triggers -Settings $settings -Principal $principal -Force | Out-Null
|
||||
|
||||
(@{
|
||||
task_name = $TaskName
|
||||
installed = $true
|
||||
poll_seconds = $PollSeconds
|
||||
dry_run = [bool]$DryRun
|
||||
} | ConvertTo-Json -Depth 20)
|
||||
137
ralph/scripts/Invoke-ClaudeProvider.ps1
Normal file
137
ralph/scripts/Invoke-ClaudeProvider.ps1
Normal file
@@ -0,0 +1,137 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$ProviderName,
|
||||
[Parameter(Mandatory = $true)][string]$PromptFile,
|
||||
[Parameter(Mandatory = $true)][string]$OutputFile,
|
||||
[string]$WorkingDirectory = "",
|
||||
[string[]]$AddDirectories = @(),
|
||||
[ValidateSet("json", "text")][string]$OutputFormat = "json"
|
||||
)
|
||||
|
||||
. (Join-Path $PSScriptRoot "Common.ps1")
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($WorkingDirectory)) {
|
||||
$WorkingDirectory = Get-RepoRoot
|
||||
}
|
||||
|
||||
$provider = Get-ProviderConfig -Name $ProviderName
|
||||
$runner = [string]$provider.runner
|
||||
$promptText = Get-Content -Raw -Path $PromptFile
|
||||
Ensure-Directory -Path (Split-Path -Parent $OutputFile)
|
||||
|
||||
if ($runner -eq "opencode") {
|
||||
$opencodeCommand = Get-Command opencode -ErrorAction SilentlyContinue
|
||||
if ($null -eq $opencodeCommand) {
|
||||
throw "opencode executable not found in PATH."
|
||||
}
|
||||
|
||||
$arguments = @(
|
||||
"run",
|
||||
"--dir", $WorkingDirectory,
|
||||
"--model", [string]$provider.model,
|
||||
"--format", "json"
|
||||
)
|
||||
$agentProp = $provider.PSObject.Properties["agent"]
|
||||
if ($null -ne $agentProp -and -not [string]::IsNullOrWhiteSpace([string]$agentProp.Value)) {
|
||||
$arguments += @("--agent", [string]$agentProp.Value)
|
||||
}
|
||||
$variantProp = $provider.PSObject.Properties["variant"]
|
||||
if ($null -ne $variantProp -and -not [string]::IsNullOrWhiteSpace([string]$variantProp.Value)) {
|
||||
$arguments += @("--variant", [string]$variantProp.Value)
|
||||
}
|
||||
$arguments += @($promptText)
|
||||
|
||||
$output = @(& $opencodeCommand.Source @arguments 2>&1)
|
||||
$exitCode = $LASTEXITCODE
|
||||
|
||||
$result = [ordered]@{
|
||||
provider = $ProviderName
|
||||
runner = "opencode"
|
||||
model = [string]$provider.model
|
||||
working_directory = $WorkingDirectory
|
||||
prompt_file = $PromptFile
|
||||
output_format = "json"
|
||||
timestamp = (Get-Date).ToString("o")
|
||||
exit_code = $exitCode
|
||||
output = ($output -join [Environment]::NewLine)
|
||||
}
|
||||
|
||||
Write-Utf8File -Path $OutputFile -Content (($result | ConvertTo-Json -Depth 100) + "`n")
|
||||
|
||||
if ($exitCode -ne 0) {
|
||||
throw "Provider '$ProviderName' failed with exit code $exitCode. See $OutputFile"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
$envNames = @(
|
||||
"ANTHROPIC_BASE_URL",
|
||||
"ANTHROPIC_AUTH_TOKEN",
|
||||
"API_TIMEOUT_MS",
|
||||
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC",
|
||||
"ANTHROPIC_MODEL",
|
||||
"ANTHROPIC_SMALL_FAST_MODEL",
|
||||
"ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
||||
"ANTHROPIC_DEFAULT_SONNET_MODEL",
|
||||
"ANTHROPIC_DEFAULT_OPUS_MODEL",
|
||||
"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS"
|
||||
)
|
||||
|
||||
$previous = @{}
|
||||
foreach ($name in $envNames) {
|
||||
$previous[$name] = [Environment]::GetEnvironmentVariable($name, "Process")
|
||||
}
|
||||
|
||||
try {
|
||||
[Environment]::SetEnvironmentVariable("ANTHROPIC_BASE_URL", [string]$provider.base_url, "Process")
|
||||
[Environment]::SetEnvironmentVariable("ANTHROPIC_AUTH_TOKEN", [string]$provider.auth_token, "Process")
|
||||
[Environment]::SetEnvironmentVariable("API_TIMEOUT_MS", [string]$provider.timeout_ms, "Process")
|
||||
[Environment]::SetEnvironmentVariable("CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC", "1", "Process")
|
||||
[Environment]::SetEnvironmentVariable("ANTHROPIC_MODEL", [string]$provider.model, "Process")
|
||||
[Environment]::SetEnvironmentVariable("ANTHROPIC_SMALL_FAST_MODEL", [string]$provider.model, "Process")
|
||||
[Environment]::SetEnvironmentVariable("ANTHROPIC_DEFAULT_HAIKU_MODEL", [string]$provider.model, "Process")
|
||||
[Environment]::SetEnvironmentVariable("ANTHROPIC_DEFAULT_SONNET_MODEL", [string]$provider.model, "Process")
|
||||
[Environment]::SetEnvironmentVariable("ANTHROPIC_DEFAULT_OPUS_MODEL", [string]$provider.model, "Process")
|
||||
[Environment]::SetEnvironmentVariable(
|
||||
"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS",
|
||||
$(if ($provider.experimental_agent_teams) { "1" } else { "0" }),
|
||||
"Process"
|
||||
)
|
||||
|
||||
$claudeArgs = @(
|
||||
"-p",
|
||||
"--output-format", $OutputFormat,
|
||||
"--dangerously-skip-permissions",
|
||||
"--model", [string]$provider.model
|
||||
)
|
||||
|
||||
foreach ($path in @($WorkingDirectory) + $AddDirectories) {
|
||||
if (-not [string]::IsNullOrWhiteSpace($path)) {
|
||||
$claudeArgs += @("--add-dir", $path)
|
||||
}
|
||||
}
|
||||
|
||||
$output = $promptText | & claude @claudeArgs "-" 2>&1
|
||||
$exitCode = $LASTEXITCODE
|
||||
}
|
||||
finally {
|
||||
foreach ($name in $envNames) {
|
||||
[Environment]::SetEnvironmentVariable($name, $previous[$name], "Process")
|
||||
}
|
||||
}
|
||||
|
||||
$result = [ordered]@{
|
||||
provider = $ProviderName
|
||||
model = [string]$provider.model
|
||||
working_directory = $WorkingDirectory
|
||||
prompt_file = $PromptFile
|
||||
output_format = $OutputFormat
|
||||
timestamp = (Get-Date).ToString("o")
|
||||
exit_code = $exitCode
|
||||
output = ($output -join [Environment]::NewLine)
|
||||
}
|
||||
|
||||
Write-Utf8File -Path $OutputFile -Content (($result | ConvertTo-Json -Depth 100) + "`n")
|
||||
|
||||
if ($exitCode -ne 0) {
|
||||
throw "Provider '$ProviderName' failed with exit code $exitCode. See $OutputFile"
|
||||
}
|
||||
122
ralph/scripts/Invoke-CodexMaster.ps1
Normal file
122
ralph/scripts/Invoke-CodexMaster.ps1
Normal file
@@ -0,0 +1,122 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$PromptFile,
|
||||
[Parameter(Mandatory = $true)][string]$OutputFile,
|
||||
[string]$WorkingDirectory = "",
|
||||
[switch]$SkipVerdictParse
|
||||
)
|
||||
|
||||
. (Join-Path $PSScriptRoot "Common.ps1")
|
||||
|
||||
$codexConfig = Get-CodexConfig
|
||||
if ([string]::IsNullOrWhiteSpace($WorkingDirectory)) {
|
||||
$WorkingDirectory = [string]$codexConfig.working_directory
|
||||
}
|
||||
|
||||
Ensure-Directory -Path (Split-Path -Parent $OutputFile)
|
||||
|
||||
$promptText = Get-Content -Raw -Path $PromptFile
|
||||
$preferredCmd = Join-Path $env:APPDATA "npm\codex.cmd"
|
||||
$preferredPs1 = Join-Path $env:APPDATA "npm\codex.ps1"
|
||||
if (Test-Path $preferredCmd) {
|
||||
$codexPath = $preferredCmd
|
||||
}
|
||||
elseif (Test-Path $preferredPs1) {
|
||||
$codexPath = $preferredPs1
|
||||
}
|
||||
else {
|
||||
$codexCommand = Get-Command codex -ErrorAction SilentlyContinue
|
||||
if ($null -eq $codexCommand) {
|
||||
throw "codex executable not found in PATH."
|
||||
}
|
||||
$codexPath = $codexCommand.Source
|
||||
}
|
||||
|
||||
Write-Utf8File -Path $PromptFile -Content $promptText
|
||||
|
||||
$stdoutLog = $OutputFile + ".stdout.log"
|
||||
$stderrLog = $OutputFile + ".stderr.log"
|
||||
$model = [string]$codexConfig.model
|
||||
$sessionId = [string]$codexConfig.session_id
|
||||
if ([string]::IsNullOrWhiteSpace($sessionId)) {
|
||||
throw "codex.local.json is missing session_id"
|
||||
}
|
||||
|
||||
$arguments = @(
|
||||
"-C", $WorkingDirectory,
|
||||
"exec", "resume", $sessionId,
|
||||
"--dangerously-bypass-approvals-and-sandbox"
|
||||
)
|
||||
if (-not [string]::IsNullOrWhiteSpace($model)) {
|
||||
$arguments += @("--model", $model)
|
||||
}
|
||||
$arguments += @("--output-last-message", $OutputFile, "-")
|
||||
|
||||
$stdout = @()
|
||||
$stderr = @()
|
||||
try {
|
||||
$mergedOutput = @($promptText | & $codexPath @arguments 2>&1)
|
||||
$exitCode = $LASTEXITCODE
|
||||
foreach ($entry in $mergedOutput) {
|
||||
if ($entry -is [System.Management.Automation.ErrorRecord]) {
|
||||
$stderr += $entry.ToString()
|
||||
}
|
||||
else {
|
||||
$stdout += [string]$entry
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$exitCode = 1
|
||||
$stderr = @($_.Exception.Message)
|
||||
}
|
||||
|
||||
if ($stdout) {
|
||||
Write-Utf8File -Path $stdoutLog -Content ((@($stdout) -join [Environment]::NewLine) + [Environment]::NewLine)
|
||||
}
|
||||
elseif (-not (Test-Path $stdoutLog)) {
|
||||
Write-Utf8File -Path $stdoutLog -Content ""
|
||||
}
|
||||
|
||||
if ($stderr) {
|
||||
Write-Utf8File -Path $stderrLog -Content ((@($stderr) -join [Environment]::NewLine) + [Environment]::NewLine)
|
||||
}
|
||||
elseif (-not (Test-Path $stderrLog)) {
|
||||
Write-Utf8File -Path $stderrLog -Content ""
|
||||
}
|
||||
|
||||
if (-not $SkipVerdictParse) {
|
||||
if (Test-Path $OutputFile) {
|
||||
try {
|
||||
$parsedVerdict = Read-CodexReviewVerdict -Path $OutputFile
|
||||
if ($exitCode -ne 0) {
|
||||
$stderr += "Codex returned exit code $exitCode but produced a valid review output. Accepting the review output."
|
||||
Write-Utf8File -Path $stderrLog -Content ((@($stderr) -join [Environment]::NewLine) + [Environment]::NewLine)
|
||||
}
|
||||
return $parsedVerdict
|
||||
}
|
||||
catch {
|
||||
if ($exitCode -eq 0) {
|
||||
throw
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif (Test-Path $OutputFile) {
|
||||
$rawOutput = (Get-Content -Raw -Path $OutputFile).Trim()
|
||||
if (-not [string]::IsNullOrWhiteSpace($rawOutput)) {
|
||||
if ($exitCode -ne 0) {
|
||||
$stderr += "Codex returned exit code $exitCode but produced non-empty output. Accepting the generated output."
|
||||
Write-Utf8File -Path $stderrLog -Content ((@($stderr) -join [Environment]::NewLine) + [Environment]::NewLine)
|
||||
}
|
||||
return $rawOutput
|
||||
}
|
||||
}
|
||||
|
||||
if ($exitCode -ne 0) {
|
||||
$errorExcerpt = ""
|
||||
$combined = ((@($stdout) + @($stderr)) -join " ").Trim()
|
||||
if ($combined -match "usage limit") {
|
||||
$errorExcerpt = " Codex local CLI is currently over its usage limit."
|
||||
}
|
||||
throw "Codex master review failed with exit code $exitCode.$errorExcerpt See $stdoutLog and $stderrLog"
|
||||
}
|
||||
44
ralph/scripts/Invoke-CodexNextTask.ps1
Normal file
44
ralph/scripts/Invoke-CodexNextTask.ps1
Normal file
@@ -0,0 +1,44 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$TaskDirectory,
|
||||
[Parameter(Mandatory = $true)][string]$RunDirectory,
|
||||
[Parameter(Mandatory = $true)][string]$OutputFile,
|
||||
[Parameter(Mandatory = $true)][string]$RunStatus,
|
||||
[string]$WorkingDirectory = "",
|
||||
[string[]]$ExtraSections = @()
|
||||
)
|
||||
|
||||
. (Join-Path $PSScriptRoot "Common.ps1")
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($WorkingDirectory)) {
|
||||
$WorkingDirectory = Get-RepoRoot
|
||||
}
|
||||
|
||||
$taskPack = Read-TaskPack -TaskDirectory $TaskDirectory
|
||||
$ralphRoot = Get-RalphRoot
|
||||
$promptPath = Join-Path $RunDirectory "prompts\codex_next_task.md"
|
||||
|
||||
$sections = @(
|
||||
"## PREVIOUS RUN DIRECTORY`n$RunDirectory",
|
||||
"## PREVIOUS RUN STATUS`n$RunStatus"
|
||||
)
|
||||
foreach ($section in $ExtraSections) {
|
||||
$sections += $section
|
||||
}
|
||||
|
||||
New-PromptDocument `
|
||||
-TemplatePath (Join-Path $ralphRoot "templates\CODEX_NEXT_TASK_PROMPT.md") `
|
||||
-TaskPack $taskPack `
|
||||
-OutputPath $promptPath `
|
||||
-ExtraSections $sections
|
||||
|
||||
& (Join-Path $PSScriptRoot "Invoke-CodexMaster.ps1") `
|
||||
-PromptFile $promptPath `
|
||||
-OutputFile $OutputFile `
|
||||
-WorkingDirectory $WorkingDirectory `
|
||||
-SkipVerdictParse | Out-Null
|
||||
|
||||
if (-not (Test-Path $OutputFile)) {
|
||||
throw "Codex next-task generation did not produce output: $OutputFile"
|
||||
}
|
||||
|
||||
Get-Content -Raw -Path $OutputFile
|
||||
152
ralph/scripts/Queue-RalphFollowupFromCurrentRun.ps1
Normal file
152
ralph/scripts/Queue-RalphFollowupFromCurrentRun.ps1
Normal file
@@ -0,0 +1,152 @@
|
||||
param(
|
||||
[string]$RunId = "",
|
||||
[string]$RunStatus = "",
|
||||
[switch]$Enqueue
|
||||
)
|
||||
|
||||
. (Join-Path $PSScriptRoot "Common.ps1")
|
||||
|
||||
$ralphRoot = Get-RalphRoot
|
||||
$repoRoot = Get-RepoRoot
|
||||
$automationConfig = Get-RalphAutomationConfig
|
||||
if ($null -eq $automationConfig -or -not ($automationConfig.PSObject.Properties.Name -contains "auto_followup")) {
|
||||
throw "automation config missing auto_followup block"
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($RunId)) {
|
||||
$current = Read-JsonFile -Path (Get-RalphStateFile -Name "current_run.json")
|
||||
}
|
||||
else {
|
||||
$runDir = Join-Path $ralphRoot ("runs\" + $RunId)
|
||||
if (-not (Test-Path $runDir)) {
|
||||
throw "Run directory not found: $runDir"
|
||||
}
|
||||
$currentRunFile = Get-RalphStateFile -Name "current_run.json"
|
||||
$current = Read-JsonFile -Path $currentRunFile
|
||||
if ([string]$current.run_id -ne $RunId) {
|
||||
$summaryPath = Join-Path $runDir "SUMMARY.md"
|
||||
$taskPath = Join-Path $runDir "TASK.md"
|
||||
$acceptancePath = Join-Path $runDir "ACCEPTANCE.md"
|
||||
$contextPath = Join-Path $runDir "CONTEXT.md"
|
||||
if (-not (Test-Path $taskPath)) {
|
||||
throw "Run does not contain TASK.md: $runDir"
|
||||
}
|
||||
$current = [ordered]@{
|
||||
run_id = $RunId
|
||||
run_dir = $runDir
|
||||
task_directory = ""
|
||||
status = "unknown"
|
||||
latest_message = $(if (Test-Path $summaryPath) { (Get-Content -Raw $summaryPath) } else { "" })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($RunStatus)) {
|
||||
$RunStatus = [string]$current.status
|
||||
}
|
||||
|
||||
$runDirResolved = [string]$current.run_dir
|
||||
if ([string]::IsNullOrWhiteSpace($runDirResolved)) {
|
||||
throw "current run state is missing run_dir"
|
||||
}
|
||||
|
||||
$taskDirectory = [string]$current.task_directory
|
||||
if ([string]::IsNullOrWhiteSpace($taskDirectory) -or -not (Test-Path $taskDirectory)) {
|
||||
$taskLeaf = ""
|
||||
try {
|
||||
$taskLeaf = Split-Path -Leaf ([string]$current.task_directory)
|
||||
}
|
||||
catch {
|
||||
$taskLeaf = ""
|
||||
}
|
||||
$searchRoots = @(
|
||||
(Join-Path $ralphRoot "tasks\\processing"),
|
||||
(Join-Path $ralphRoot "tasks\\failed"),
|
||||
(Join-Path $ralphRoot "tasks\\completed")
|
||||
)
|
||||
foreach ($root in $searchRoots) {
|
||||
if (-not (Test-Path $root)) {
|
||||
continue
|
||||
}
|
||||
$candidate = $null
|
||||
if (-not [string]::IsNullOrWhiteSpace($taskLeaf)) {
|
||||
$candidate = Join-Path $root $taskLeaf
|
||||
if (Test-Path $candidate) {
|
||||
$taskDirectory = $candidate
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
$matches = @(Get-ChildItem -Path $root -Directory -ErrorAction SilentlyContinue | Where-Object {
|
||||
$_.Name -like ("*" + [string]$current.run_id + "*") -or $_.Name -like ("*" + $taskLeaf + "*")
|
||||
} | Select-Object -First 1)
|
||||
if ($matches.Count -gt 0) {
|
||||
$taskDirectory = $matches[0].FullName
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($taskDirectory) -or -not (Test-Path $taskDirectory)) {
|
||||
throw "could not resolve task directory for followup generation"
|
||||
}
|
||||
|
||||
$targetRelative = "docs\\autopilot"
|
||||
if ($automationConfig.auto_followup.PSObject.Properties.Name -contains "target_directory" -and -not [string]::IsNullOrWhiteSpace([string]$automationConfig.auto_followup.target_directory)) {
|
||||
$targetRelative = [string]$automationConfig.auto_followup.target_directory
|
||||
}
|
||||
$targetDirectory = Join-Path $repoRoot $targetRelative
|
||||
Ensure-Directory -Path $targetDirectory
|
||||
|
||||
$outputFile = Join-Path $runDirResolved "NEXT_TASK_MANUAL.md"
|
||||
$extraSections = @(
|
||||
"## PREVIOUS OUTCOME SUMMARY`n$([string]$current.latest_message)",
|
||||
"## RUN SUMMARY FILE`n$(Join-Path $runDirResolved 'SUMMARY.md')",
|
||||
"## RUN OUTPUTS DIRECTORY`n$(Join-Path $runDirResolved 'outputs')",
|
||||
"## RUN REVIEWS DIRECTORY`n$(Join-Path $runDirResolved 'reviews')"
|
||||
)
|
||||
|
||||
& (Join-Path $PSScriptRoot "Invoke-CodexNextTask.ps1") `
|
||||
-TaskDirectory $taskDirectory `
|
||||
-RunDirectory $runDirResolved `
|
||||
-OutputFile $outputFile `
|
||||
-RunStatus $RunStatus `
|
||||
-WorkingDirectory $repoRoot `
|
||||
-ExtraSections $extraSections | Out-Null
|
||||
|
||||
$slugBase = Convert-ToRalphSlug -Text ([IO.Path]::GetFileNameWithoutExtension($outputFile))
|
||||
$finalMdPath = Join-Path $targetDirectory ((Get-Date -Format "yyyyMMdd-HHmmss") + "-" + $slugBase + ".md")
|
||||
Copy-Item -Path $outputFile -Destination $finalMdPath -Force
|
||||
|
||||
$result = [ordered]@{
|
||||
generated = $true
|
||||
source_markdown = $finalMdPath
|
||||
queued = $false
|
||||
}
|
||||
|
||||
if ($Enqueue) {
|
||||
$taskInfo = New-RalphTaskPackFromMarkdown `
|
||||
-MarkdownPath $finalMdPath `
|
||||
-AdditionalMetadata @{
|
||||
auto_generated = $true
|
||||
parent_run_id = [string]$current.run_id
|
||||
parent_task_directory = $taskDirectory
|
||||
followup_generation = 1
|
||||
source_run_status = $RunStatus
|
||||
}
|
||||
$result.queued = $true
|
||||
$result.task_directory = $taskInfo.task_directory
|
||||
$result.title = $taskInfo.title
|
||||
Add-RalphEvent -RunId ([string]$current.run_id) -Stage "auto_followup" -Status "queued" -Actor "codex_master" -Message ("Manual backfill followup queued: " + $taskInfo.title) -Data @{
|
||||
task_directory = $taskInfo.task_directory
|
||||
source_markdown = $finalMdPath
|
||||
}
|
||||
Send-TelegramNotification `
|
||||
-EventName "task_queued" `
|
||||
-Title "Auto-followup queued" `
|
||||
-Message ($taskInfo.title + "`n" + $taskInfo.task_directory) `
|
||||
-RunId ([string]$current.run_id) `
|
||||
-Stage "auto_followup" `
|
||||
-Status "queued" | Out-Null
|
||||
}
|
||||
|
||||
($result | ConvertTo-Json -Depth 20)
|
||||
714
ralph/scripts/Start-RalphAutopilot.ps1
Normal file
714
ralph/scripts/Start-RalphAutopilot.ps1
Normal file
@@ -0,0 +1,714 @@
|
||||
param(
|
||||
[string]$TaskDirectory = "",
|
||||
[string]$RunLabel = "autopilot",
|
||||
[string]$Implementer = "",
|
||||
[string[]]$Reviewers = @(),
|
||||
[switch]$UseCodexMaster = $true,
|
||||
[switch]$AutoFix = $true,
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
. (Join-Path $PSScriptRoot "Common.ps1")
|
||||
|
||||
$ralphRoot = Get-RalphRoot
|
||||
$repoRoot = Get-RepoRoot
|
||||
$roots = Get-RalphTaskRoots
|
||||
$automationConfig = Get-RalphAutomationConfig
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($TaskDirectory)) {
|
||||
$TaskDirectory = Join-Path $ralphRoot "tasks\current"
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($Implementer)) {
|
||||
$Implementer = Get-DefaultImplementer
|
||||
}
|
||||
|
||||
if ($Reviewers.Count -eq 0) {
|
||||
$Reviewers = Get-DefaultReviewers
|
||||
}
|
||||
|
||||
$taskPack = Read-TaskPack -TaskDirectory $TaskDirectory
|
||||
$submissionPath = Join-Path $TaskDirectory "submission.json"
|
||||
$submissionMetadata = $null
|
||||
if (Test-Path $submissionPath) {
|
||||
try {
|
||||
$submissionMetadata = Read-JsonFile -Path $submissionPath
|
||||
}
|
||||
catch {
|
||||
$submissionMetadata = $null
|
||||
}
|
||||
}
|
||||
$runId = New-RunId -Label $RunLabel
|
||||
$runDir = Join-Path $ralphRoot ("runs\" + $runId)
|
||||
$worktreePath = Join-Path $ralphRoot ("worktrees\" + $runId + "-" + $Implementer)
|
||||
|
||||
Ensure-Directory -Path $runDir
|
||||
Ensure-Directory -Path (Join-Path $runDir "prompts")
|
||||
Ensure-Directory -Path (Join-Path $runDir "outputs")
|
||||
Ensure-Directory -Path (Join-Path $runDir "reviews")
|
||||
|
||||
Copy-Item -Path $taskPack.Files.Task -Destination (Join-Path $runDir "TASK.md") -Force
|
||||
Copy-Item -Path $taskPack.Files.Acceptance -Destination (Join-Path $runDir "ACCEPTANCE.md") -Force
|
||||
Copy-Item -Path $taskPack.Files.Context -Destination (Join-Path $runDir "CONTEXT.md") -Force
|
||||
|
||||
$script:runState = [ordered]@{
|
||||
run_id = $runId
|
||||
status = "initializing"
|
||||
stage = "initializing"
|
||||
started_at = (Get-Date).ToString("o")
|
||||
finished_at = $null
|
||||
task_directory = $TaskDirectory
|
||||
run_dir = $runDir
|
||||
worktree = $worktreePath
|
||||
implementer = [ordered]@{
|
||||
name = $Implementer
|
||||
status = "pending"
|
||||
started_at = $null
|
||||
finished_at = $null
|
||||
output_file = ""
|
||||
}
|
||||
reviewers = @()
|
||||
codex_master = [ordered]@{
|
||||
enabled = [bool]$UseCodexMaster
|
||||
status = $(if ($UseCodexMaster) { "pending" } else { "disabled" })
|
||||
started_at = $null
|
||||
finished_at = $null
|
||||
output_file = ""
|
||||
verdict = ""
|
||||
acceptance_passed = $false
|
||||
}
|
||||
fix_pass = [ordered]@{
|
||||
enabled = [bool]$AutoFix
|
||||
status = $(if ($AutoFix) { "pending" } else { "disabled" })
|
||||
started_at = $null
|
||||
finished_at = $null
|
||||
output_file = ""
|
||||
}
|
||||
latest_message = "Run initialized"
|
||||
errors = @()
|
||||
}
|
||||
|
||||
foreach ($reviewer in $Reviewers) {
|
||||
$script:runState.reviewers += [ordered]@{
|
||||
name = $reviewer
|
||||
status = "pending"
|
||||
started_at = $null
|
||||
finished_at = $null
|
||||
output_file = ""
|
||||
}
|
||||
}
|
||||
|
||||
function Get-FollowupGeneration {
|
||||
if ($null -eq $submissionMetadata) {
|
||||
return 0
|
||||
}
|
||||
if ($submissionMetadata.PSObject.Properties.Name -contains "followup_generation") {
|
||||
try {
|
||||
return [int]$submissionMetadata.followup_generation
|
||||
}
|
||||
catch {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
function Save-RunState {
|
||||
Set-RalphCurrentRunState -State $script:runState
|
||||
}
|
||||
|
||||
function Set-RunPhase {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Stage,
|
||||
[Parameter(Mandatory = $true)][string]$Status,
|
||||
[Parameter(Mandatory = $true)][string]$Message
|
||||
)
|
||||
|
||||
$script:runState.stage = $Stage
|
||||
$script:runState.status = $Status
|
||||
$script:runState.latest_message = $Message
|
||||
Save-RunState
|
||||
Add-RalphEvent -RunId $script:runState.run_id -Stage $Stage -Status $Status -Message $Message
|
||||
}
|
||||
|
||||
function Set-ActorState {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][ValidateSet("implementer", "reviewer", "codex_master", "fix_pass")][string]$ActorType,
|
||||
[Parameter(Mandatory = $true)][string]$Status,
|
||||
[Parameter(Mandatory = $true)][string]$Message,
|
||||
[string]$ActorName = "",
|
||||
[string]$OutputFile = ""
|
||||
)
|
||||
|
||||
$timestamp = (Get-Date).ToString("o")
|
||||
$target = $null
|
||||
$actorLabel = $ActorName
|
||||
|
||||
switch ($ActorType) {
|
||||
"implementer" {
|
||||
$target = $script:runState.implementer
|
||||
$actorLabel = $script:runState.implementer.name
|
||||
}
|
||||
"reviewer" {
|
||||
$target = $script:runState.reviewers | Where-Object { $_.name -eq $ActorName } | Select-Object -First 1
|
||||
$actorLabel = $ActorName
|
||||
}
|
||||
"codex_master" {
|
||||
$target = $script:runState.codex_master
|
||||
$actorLabel = "codex_master"
|
||||
}
|
||||
"fix_pass" {
|
||||
$target = $script:runState.fix_pass
|
||||
$actorLabel = $script:runState.implementer.name
|
||||
}
|
||||
}
|
||||
|
||||
if ($null -ne $target) {
|
||||
$target.status = $Status
|
||||
if ($Status -eq "running" -and -not $target.started_at) {
|
||||
$target.started_at = $timestamp
|
||||
}
|
||||
if ($Status -in @("completed", "failed", "skipped")) {
|
||||
$target.finished_at = $timestamp
|
||||
}
|
||||
if ($OutputFile) {
|
||||
$target.output_file = $OutputFile
|
||||
}
|
||||
}
|
||||
|
||||
$script:runState.latest_message = $Message
|
||||
Save-RunState
|
||||
Add-RalphEvent -RunId $script:runState.run_id -Stage $ActorType -Status $Status -Actor $actorLabel -Message $Message -Data @{
|
||||
output_file = $OutputFile
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-CodexReviewPass {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$StageName,
|
||||
[Parameter(Mandatory = $true)][string]$PromptFileName,
|
||||
[Parameter(Mandatory = $true)][string]$OutputFileName,
|
||||
[Parameter(Mandatory = $true)][string]$RunningMessage,
|
||||
[Parameter(Mandatory = $true)][string]$CompletedMessage,
|
||||
[string[]]$ExtraSections = @()
|
||||
)
|
||||
|
||||
$promptPath = Join-Path $runDir ("prompts\" + $PromptFileName)
|
||||
$outputPath = Join-Path $runDir ("reviews\" + $OutputFileName)
|
||||
|
||||
New-PromptDocument `
|
||||
-TemplatePath (Join-Path $ralphRoot "templates\CODEX_REVIEW_PROMPT.md") `
|
||||
-TaskPack $taskPack `
|
||||
-OutputPath $promptPath `
|
||||
-ExtraSections $ExtraSections
|
||||
|
||||
Set-RunPhase -Stage $StageName -Status "running" -Message $RunningMessage
|
||||
Set-ActorState -ActorType "codex_master" -Status "running" -Message $RunningMessage -OutputFile $outputPath
|
||||
$verdict = & (Join-Path $PSScriptRoot "Invoke-CodexMaster.ps1") `
|
||||
-PromptFile $promptPath `
|
||||
-OutputFile $outputPath `
|
||||
-WorkingDirectory $worktreePath
|
||||
|
||||
$script:runState.codex_master.verdict = [string]$verdict.verdict
|
||||
$script:runState.codex_master.acceptance_passed = [bool]$verdict.acceptance_passed
|
||||
Set-ActorState -ActorType "codex_master" -Status "completed" -Message ($CompletedMessage + " (verdict: " + $verdict.verdict + ")") -OutputFile $outputPath
|
||||
Set-RunPhase -Stage $StageName -Status "completed" -Message ($CompletedMessage + " (verdict: " + $verdict.verdict + ")")
|
||||
Save-RunState
|
||||
return $verdict
|
||||
}
|
||||
|
||||
function Invoke-AutoFollowupTask {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$OutcomeStatus,
|
||||
[Parameter(Mandatory = $true)][string]$OutcomeSummary
|
||||
)
|
||||
|
||||
if ($null -eq $automationConfig) {
|
||||
return $null
|
||||
}
|
||||
if (-not ($automationConfig.PSObject.Properties.Name -contains "auto_followup")) {
|
||||
return $null
|
||||
}
|
||||
|
||||
$autoFollowup = $automationConfig.auto_followup
|
||||
$enabled = $false
|
||||
try { $enabled = [bool]$autoFollowup.enabled } catch { $enabled = $false }
|
||||
if (-not $enabled) {
|
||||
return $null
|
||||
}
|
||||
if (-not $UseCodexMaster) {
|
||||
return $null
|
||||
}
|
||||
|
||||
$trigger = $false
|
||||
if ($OutcomeStatus -eq "failed") {
|
||||
try { $trigger = [bool]$autoFollowup.trigger_on_failure } catch { $trigger = $false }
|
||||
}
|
||||
elseif ($OutcomeStatus -eq "completed") {
|
||||
try { $trigger = [bool]$autoFollowup.trigger_on_success } catch { $trigger = $false }
|
||||
}
|
||||
if (-not $trigger) {
|
||||
return $null
|
||||
}
|
||||
|
||||
$maxDepth = 0
|
||||
try { $maxDepth = [int]$autoFollowup.max_chain_depth } catch { $maxDepth = 0 }
|
||||
$currentDepth = Get-FollowupGeneration
|
||||
if ($maxDepth -gt 0 -and $currentDepth -ge $maxDepth) {
|
||||
return $null
|
||||
}
|
||||
|
||||
$targetRelative = "docs\autopilot"
|
||||
if ($autoFollowup.PSObject.Properties.Name -contains "target_directory" -and -not [string]::IsNullOrWhiteSpace([string]$autoFollowup.target_directory)) {
|
||||
$targetRelative = [string]$autoFollowup.target_directory
|
||||
}
|
||||
$targetDirectory = Join-Path $repoRoot $targetRelative
|
||||
Ensure-Directory -Path $targetDirectory
|
||||
|
||||
$prefix = "AUTOFOLLOWUP"
|
||||
if ($autoFollowup.PSObject.Properties.Name -contains "title_prefix" -and -not [string]::IsNullOrWhiteSpace([string]$autoFollowup.title_prefix)) {
|
||||
$prefix = [string]$autoFollowup.title_prefix
|
||||
}
|
||||
|
||||
$nextPromptOutput = Join-Path $runDir "NEXT_TASK.md"
|
||||
$extraSections = @(
|
||||
"## PREVIOUS OUTCOME SUMMARY`n$OutcomeSummary",
|
||||
"## RUN SUMMARY FILE`n$(Join-Path $runDir 'SUMMARY.md')",
|
||||
"## RUN OUTPUTS DIRECTORY`n$(Join-Path $runDir 'outputs')",
|
||||
"## RUN REVIEWS DIRECTORY`n$(Join-Path $runDir 'reviews')"
|
||||
)
|
||||
|
||||
$null = & (Join-Path $PSScriptRoot "Invoke-CodexNextTask.ps1") `
|
||||
-TaskDirectory $TaskDirectory `
|
||||
-RunDirectory $runDir `
|
||||
-OutputFile $nextPromptOutput `
|
||||
-RunStatus $OutcomeStatus `
|
||||
-WorkingDirectory $worktreePath `
|
||||
-ExtraSections $extraSections
|
||||
|
||||
$dateLabel = Get-Date -Format "yyyyMMdd-HHmmss"
|
||||
$fileName = "{0}-{1}.md" -f $dateLabel, (Convert-ToRalphSlug -Text ($prefix + "-" + $taskPack.Task.Substring(0, [Math]::Min($taskPack.Task.Length, 32))))
|
||||
$finalMdPath = Join-Path $targetDirectory $fileName
|
||||
Copy-Item -Path $nextPromptOutput -Destination $finalMdPath -Force
|
||||
|
||||
$taskInfo = New-RalphTaskPackFromMarkdown `
|
||||
-MarkdownPath $finalMdPath `
|
||||
-AdditionalMetadata @{
|
||||
auto_generated = $true
|
||||
parent_run_id = $runId
|
||||
parent_task_directory = $TaskDirectory
|
||||
followup_generation = ($currentDepth + 1)
|
||||
source_run_status = $OutcomeStatus
|
||||
}
|
||||
|
||||
Add-RalphEvent -RunId $runId -Stage "auto_followup" -Status "queued" -Actor "codex_master" -Message ("Auto-followup task queued: " + $taskInfo.title) -Data @{
|
||||
task_directory = $taskInfo.task_directory
|
||||
source_markdown = $finalMdPath
|
||||
}
|
||||
Send-TelegramNotification `
|
||||
-EventName "task_queued" `
|
||||
-Title "Auto-followup queued" `
|
||||
-Message ($taskInfo.title + "`n" + $taskInfo.task_directory) `
|
||||
-RunId $runId `
|
||||
-Stage "auto_followup" `
|
||||
-Status "queued" | Out-Null
|
||||
|
||||
return [ordered]@{
|
||||
title = $taskInfo.title
|
||||
task_directory = $taskInfo.task_directory
|
||||
source_markdown = $finalMdPath
|
||||
followup_generation = ($currentDepth + 1)
|
||||
}
|
||||
}
|
||||
|
||||
Save-RunState
|
||||
Add-RalphEvent -RunId $runId -Stage "initializing" -Status "started" -Message "Ralph run initialized" -Data @{
|
||||
implementer = $Implementer
|
||||
reviewers = $Reviewers
|
||||
worktree = $worktreePath
|
||||
}
|
||||
Send-TelegramNotification `
|
||||
-EventName "run_started" `
|
||||
-Title "Run started" `
|
||||
-Message ("Implementer: " + $Implementer + "`nReviewers: " + ($Reviewers -join ", ")) `
|
||||
-RunId $runId `
|
||||
-Stage "initializing" `
|
||||
-Status "started" | Out-Null
|
||||
|
||||
$gitStatus = & git -C $repoRoot status --short
|
||||
Write-Utf8File -Path (Join-Path $runDir "repo_status_before.txt") -Content (($gitStatus -join "`n") + "`n")
|
||||
|
||||
$gitStat = & git -C $repoRoot diff --stat
|
||||
Write-Utf8File -Path (Join-Path $runDir "repo_diff_stat_before.txt") -Content (($gitStat -join "`n") + "`n")
|
||||
|
||||
$implementerPrompt = Join-Path $runDir "prompts\implementer.md"
|
||||
New-PromptDocument `
|
||||
-TemplatePath (Join-Path $ralphRoot "templates\IMPLEMENTER_PROMPT.md") `
|
||||
-TaskPack $taskPack `
|
||||
-OutputPath $implementerPrompt `
|
||||
-ExtraSections @(
|
||||
"## WORKTREE`nEdit only inside this worktree:`n`n$worktreePath",
|
||||
"## REQUIRED RUN OUTPUT`nWrite a file named CHANGES.md in this run directory:`n`n$runDir"
|
||||
)
|
||||
|
||||
$summaryLines = @(
|
||||
"# Ralph Run",
|
||||
"",
|
||||
"- Run ID: $runId",
|
||||
"- Implementer: $Implementer",
|
||||
"- Reviewers: $(($Reviewers -join ', '))",
|
||||
"- Worktree: $worktreePath",
|
||||
"- Codex master review: $(if ($UseCodexMaster) { 'enabled' } else { 'disabled' })",
|
||||
"- Auto fix pass: $(if ($AutoFix) { 'enabled' } else { 'disabled' })",
|
||||
"- Dry run: $(if ($DryRun) { 'yes' } else { 'no' })"
|
||||
)
|
||||
|
||||
if ($DryRun) {
|
||||
Set-RunPhase -Stage "dry_run" -Status "completed" -Message "Dry run prepared successfully"
|
||||
$script:runState.finished_at = (Get-Date).ToString("o")
|
||||
Save-RunState
|
||||
Write-Utf8File -Path (Join-Path $runDir "SUMMARY.md") -Content (($summaryLines -join "`n") + "`n")
|
||||
Get-Content -Raw -Path (Join-Path $runDir "SUMMARY.md")
|
||||
return
|
||||
}
|
||||
|
||||
Set-RunPhase -Stage "worktree" -Status "running" -Message "Creating isolated worktree"
|
||||
& git -C $repoRoot worktree add --detach $worktreePath HEAD | Out-Null
|
||||
Set-RunPhase -Stage "worktree" -Status "completed" -Message "Worktree ready"
|
||||
|
||||
$reviewOutputs = @()
|
||||
$codexFinalVerdict = $null
|
||||
$autoFollowupResult = $null
|
||||
$script:heartbeatJob = $null
|
||||
$script:heartbeatSignalPath = ""
|
||||
$codexReviewSections = @(
|
||||
"## IMPLEMENTER WORKTREE`n$worktreePath",
|
||||
"## IMPLEMENTER DIFF FILE`n$(Join-Path $runDir 'implementer.patch')",
|
||||
"## IMPLEMENTER STATUS FILE`n$(Join-Path $runDir 'implementer_status.txt')"
|
||||
)
|
||||
|
||||
function Start-HeartbeatMonitor {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Stage,
|
||||
[Parameter(Mandatory = $true)][string]$Title,
|
||||
[Parameter(Mandatory = $true)][string]$Message,
|
||||
[int]$IntervalSeconds = 300
|
||||
)
|
||||
|
||||
Stop-HeartbeatMonitor
|
||||
|
||||
$signalPath = Join-Path $runDir ("heartbeat-" + $Stage + ".lock")
|
||||
Write-Utf8File -Path $signalPath -Content ((Get-Date).ToString("o"))
|
||||
$commonPath = Join-Path $PSScriptRoot "Common.ps1"
|
||||
|
||||
$script:heartbeatSignalPath = $signalPath
|
||||
$script:heartbeatJob = Start-Job -ArgumentList $commonPath, $signalPath, $Stage, $Title, $Message, $runId, $IntervalSeconds -ScriptBlock {
|
||||
param($CommonPath, $SignalPath, $StageName, $TitleText, $MessageText, $RunIdValue, $IntervalValue)
|
||||
. $CommonPath
|
||||
while (Test-Path $SignalPath) {
|
||||
Start-Sleep -Seconds $IntervalValue
|
||||
if (-not (Test-Path $SignalPath)) {
|
||||
break
|
||||
}
|
||||
Send-TelegramNotification `
|
||||
-EventName "run_heartbeat" `
|
||||
-Title $TitleText `
|
||||
-Message $MessageText `
|
||||
-RunId $RunIdValue `
|
||||
-Stage $StageName `
|
||||
-Status "running" | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Stop-HeartbeatMonitor {
|
||||
if (-not [string]::IsNullOrWhiteSpace($script:heartbeatSignalPath) -and (Test-Path $script:heartbeatSignalPath)) {
|
||||
Remove-Item -LiteralPath $script:heartbeatSignalPath -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
$script:heartbeatSignalPath = ""
|
||||
|
||||
if ($null -ne $script:heartbeatJob) {
|
||||
Wait-Job -Job $script:heartbeatJob -Timeout 1 | Out-Null
|
||||
if ($script:heartbeatJob.State -eq "Running") {
|
||||
Stop-Job -Job $script:heartbeatJob -Force | Out-Null
|
||||
}
|
||||
Remove-Job -Job $script:heartbeatJob -Force -ErrorAction SilentlyContinue
|
||||
$script:heartbeatJob = $null
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$implementerOutput = Join-Path $runDir "outputs\implementer.json"
|
||||
Set-RunPhase -Stage "implementer" -Status "running" -Message ("Implementer " + $Implementer + " started")
|
||||
Set-ActorState -ActorType "implementer" -Status "running" -Message ("Implementer " + $Implementer + " received task pack") -OutputFile $implementerOutput
|
||||
Start-HeartbeatMonitor -Stage "implementer" -Title "Run heartbeat" -Message ("Implementer running: " + $Implementer)
|
||||
& (Join-Path $PSScriptRoot "Invoke-ClaudeProvider.ps1") `
|
||||
-ProviderName $Implementer `
|
||||
-PromptFile $implementerPrompt `
|
||||
-OutputFile $implementerOutput `
|
||||
-WorkingDirectory $worktreePath `
|
||||
-AddDirectories @($runDir, $repoRoot)
|
||||
Stop-HeartbeatMonitor
|
||||
Set-ActorState -ActorType "implementer" -Status "completed" -Message ("Implementer " + $Implementer + " finished first pass") -OutputFile $implementerOutput
|
||||
Send-TelegramNotification `
|
||||
-EventName "implementer_completed" `
|
||||
-Title "Implementer finished" `
|
||||
-Message ("Implementer: " + $Implementer) `
|
||||
-RunId $runId `
|
||||
-Stage "implementer" `
|
||||
-Status "completed" | Out-Null
|
||||
|
||||
$worktreeStatus = & git -C $worktreePath status --short
|
||||
Write-Utf8File -Path (Join-Path $runDir "implementer_status.txt") -Content (($worktreeStatus -join "`n") + "`n")
|
||||
|
||||
$worktreeDiff = & git -C $worktreePath diff --no-ext-diff
|
||||
Write-Utf8File -Path (Join-Path $runDir "implementer.patch") -Content (($worktreeDiff -join "`n") + "`n")
|
||||
|
||||
foreach ($reviewer in $Reviewers) {
|
||||
if ($reviewer -eq $Reviewers[0]) {
|
||||
Send-TelegramNotification `
|
||||
-EventName "reviewers_started" `
|
||||
-Title "Reviewers started" `
|
||||
-Message (($Reviewers -join ", ")) `
|
||||
-RunId $runId `
|
||||
-Stage "review" `
|
||||
-Status "running" | Out-Null
|
||||
}
|
||||
$promptPath = Join-Path $runDir ("prompts\review-" + $reviewer + ".md")
|
||||
New-PromptDocument `
|
||||
-TemplatePath (Join-Path $ralphRoot "templates\REVIEWER_PROMPT.md") `
|
||||
-TaskPack $taskPack `
|
||||
-OutputPath $promptPath `
|
||||
-ExtraSections @(
|
||||
"## IMPLEMENTER WORKTREE`n$worktreePath",
|
||||
"## IMPLEMENTER DIFF FILE`n$(Join-Path $runDir 'implementer.patch')",
|
||||
"## IMPLEMENTER STATUS FILE`n$(Join-Path $runDir 'implementer_status.txt')"
|
||||
)
|
||||
|
||||
$reviewOutput = Join-Path $runDir ("reviews\" + $reviewer + ".json")
|
||||
Set-RunPhase -Stage "review" -Status "running" -Message ("Reviewer " + $reviewer + " started")
|
||||
Set-ActorState -ActorType "reviewer" -ActorName $reviewer -Status "running" -Message ("Reviewer " + $reviewer + " analyzing diff") -OutputFile $reviewOutput
|
||||
& (Join-Path $PSScriptRoot "Invoke-ClaudeProvider.ps1") `
|
||||
-ProviderName $reviewer `
|
||||
-PromptFile $promptPath `
|
||||
-OutputFile $reviewOutput `
|
||||
-WorkingDirectory $worktreePath `
|
||||
-AddDirectories @($runDir, $repoRoot)
|
||||
Set-ActorState -ActorType "reviewer" -ActorName $reviewer -Status "completed" -Message ("Reviewer " + $reviewer + " completed review") -OutputFile $reviewOutput
|
||||
$reviewOutputs += $reviewOutput
|
||||
}
|
||||
Set-RunPhase -Stage "review" -Status "completed" -Message "All provider reviews completed"
|
||||
|
||||
$codexReviewSections += "## REVIEW FILES`n$($reviewOutputs -join "`n")"
|
||||
|
||||
if ($UseCodexMaster -and $AutoFix) {
|
||||
$null = Invoke-CodexReviewPass `
|
||||
-StageName "codex_review_pre_fix" `
|
||||
-PromptFileName "codex_master_review_pre_fix.md" `
|
||||
-OutputFileName "codex_master_pre_fix.json" `
|
||||
-RunningMessage "Codex master pre-fix review started" `
|
||||
-CompletedMessage "Codex master pre-fix review completed" `
|
||||
-ExtraSections $codexReviewSections
|
||||
}
|
||||
|
||||
if ($AutoFix) {
|
||||
$fixPrompt = Join-Path $runDir "prompts\fix_pass.md"
|
||||
$fixExtraSections = @(
|
||||
"## FIX PASS`nRead the reviewer outputs and fix the highest-signal issues only.",
|
||||
"## REVIEW FILES`n$($reviewOutputs -join "`n")"
|
||||
)
|
||||
if ($UseCodexMaster) {
|
||||
$fixExtraSections += "## CODEX REVIEW FILE`n$(Join-Path $runDir 'reviews\codex_master_pre_fix.json')"
|
||||
}
|
||||
else {
|
||||
$fixExtraSections += "## CODEX REVIEW FILE`nCodex review disabled for this run."
|
||||
}
|
||||
$fixExtraSections += "## REQUIRED RUN OUTPUT`nUpdate CHANGES.md in this run directory after the fix pass:`n`n$runDir"
|
||||
|
||||
New-PromptDocument `
|
||||
-TemplatePath (Join-Path $ralphRoot "templates\IMPLEMENTER_PROMPT.md") `
|
||||
-TaskPack $taskPack `
|
||||
-OutputPath $fixPrompt `
|
||||
-ExtraSections $fixExtraSections
|
||||
|
||||
Set-RunPhase -Stage "fix_pass" -Status "running" -Message ("Fix pass started with " + $Implementer)
|
||||
Set-ActorState -ActorType "fix_pass" -Status "running" -Message ("Implementer " + $Implementer + " applying review fixes") -OutputFile (Join-Path $runDir "outputs\fix_pass.json")
|
||||
Send-TelegramNotification `
|
||||
-EventName "fix_pass_started" `
|
||||
-Title "Fix pass started" `
|
||||
-Message ("Implementer: " + $Implementer) `
|
||||
-RunId $runId `
|
||||
-Stage "fix_pass" `
|
||||
-Status "running" | Out-Null
|
||||
Start-HeartbeatMonitor -Stage "fix_pass" -Title "Run heartbeat" -Message ("Fix pass running: " + $Implementer)
|
||||
& (Join-Path $PSScriptRoot "Invoke-ClaudeProvider.ps1") `
|
||||
-ProviderName $Implementer `
|
||||
-PromptFile $fixPrompt `
|
||||
-OutputFile (Join-Path $runDir "outputs\fix_pass.json") `
|
||||
-WorkingDirectory $worktreePath `
|
||||
-AddDirectories @($runDir, $repoRoot)
|
||||
Stop-HeartbeatMonitor
|
||||
Set-ActorState -ActorType "fix_pass" -Status "completed" -Message ("Implementer " + $Implementer + " finished fix pass") -OutputFile (Join-Path $runDir "outputs\fix_pass.json")
|
||||
Set-RunPhase -Stage "fix_pass" -Status "completed" -Message "Fix pass completed"
|
||||
|
||||
$finalDiff = & git -C $worktreePath diff --no-ext-diff
|
||||
Write-Utf8File -Path (Join-Path $runDir "final.patch") -Content (($finalDiff -join "`n") + "`n")
|
||||
}
|
||||
|
||||
$finalStatus = & git -C $worktreePath status --short
|
||||
Write-Utf8File -Path (Join-Path $runDir "final_status.txt") -Content (($finalStatus -join "`n") + "`n")
|
||||
|
||||
if ($UseCodexMaster) {
|
||||
$finalDiffPath = Join-Path $runDir "final.patch"
|
||||
if (-not (Test-Path $finalDiffPath)) {
|
||||
$finalDiffPath = Join-Path $runDir "implementer.patch"
|
||||
}
|
||||
$codexFinalSections = @(
|
||||
"## IMPLEMENTER WORKTREE`n$worktreePath",
|
||||
"## FINAL DIFF FILE`n$finalDiffPath",
|
||||
"## FINAL STATUS FILE`n$(Join-Path $runDir 'final_status.txt')",
|
||||
"## REVIEW FILES`n$($reviewOutputs -join "`n")"
|
||||
)
|
||||
if ($AutoFix) {
|
||||
$codexFinalSections += "## FIX PASS OUTPUT`n$(Join-Path $runDir 'outputs\\fix_pass.json')"
|
||||
$codexFinalSections += "## PRE-FIX CODEX REVIEW`n$(Join-Path $runDir 'reviews\\codex_master_pre_fix.json')"
|
||||
}
|
||||
|
||||
$codexFinalVerdict = Invoke-CodexReviewPass `
|
||||
-StageName "codex_review_final" `
|
||||
-PromptFileName "codex_master_review_final.md" `
|
||||
-OutputFileName "codex_master_final.json" `
|
||||
-RunningMessage "Codex master final review started" `
|
||||
-CompletedMessage "Codex master final review completed" `
|
||||
-ExtraSections $codexFinalSections
|
||||
|
||||
if (([string]$codexFinalVerdict.verdict) -ne "pass" -or -not [bool]$codexFinalVerdict.acceptance_passed) {
|
||||
Send-TelegramNotification `
|
||||
-EventName "codex_failed" `
|
||||
-Title "Codex gate rejected run" `
|
||||
-Message ([string]$codexFinalVerdict.summary) `
|
||||
-RunId $runId `
|
||||
-Stage "codex_review_final" `
|
||||
-Status "failed" | Out-Null
|
||||
throw ("Codex master rejected the run with verdict '{0}': {1}" -f $codexFinalVerdict.verdict, $codexFinalVerdict.summary)
|
||||
}
|
||||
}
|
||||
|
||||
$summaryLines += @(
|
||||
"",
|
||||
"## Outputs",
|
||||
"",
|
||||
"- Run directory: $runDir",
|
||||
"- Worktree: $worktreePath",
|
||||
"- Implementer output: $(Join-Path $runDir 'outputs\implementer.json')",
|
||||
"- Final status: $(Join-Path $runDir 'final_status.txt')",
|
||||
"- Codex final verdict: $(if ($UseCodexMaster -and $null -ne $codexFinalVerdict) { [string]$codexFinalVerdict.verdict } else { 'not-run' })"
|
||||
)
|
||||
|
||||
try {
|
||||
$autoFollowupResult = Invoke-AutoFollowupTask -OutcomeStatus "completed" -OutcomeSummary "Run completed successfully"
|
||||
}
|
||||
catch {
|
||||
$script:runState.errors += ("Auto-followup generation error: " + $_.Exception.Message)
|
||||
Save-RunState
|
||||
Add-RalphEvent -RunId $runId -Stage "auto_followup" -Status "failed" -Actor "codex_master" -Message $_.Exception.Message
|
||||
}
|
||||
|
||||
if ($null -ne $autoFollowupResult) {
|
||||
$summaryLines += @(
|
||||
"",
|
||||
"## Auto Followup",
|
||||
"",
|
||||
"- Title: $($autoFollowupResult.title)",
|
||||
"- Task directory: $($autoFollowupResult.task_directory)",
|
||||
"- Source markdown: $($autoFollowupResult.source_markdown)"
|
||||
)
|
||||
}
|
||||
|
||||
Write-Utf8File -Path (Join-Path $runDir "SUMMARY.md") -Content (($summaryLines -join "`n") + "`n")
|
||||
$script:runState.status = "completed"
|
||||
$script:runState.stage = "completed"
|
||||
$script:runState.finished_at = (Get-Date).ToString("o")
|
||||
$script:runState.latest_message = "Run completed successfully"
|
||||
Save-RunState
|
||||
Add-RalphEvent -RunId $runId -Stage "completed" -Status "completed" -Message "Ralph run completed successfully" -Data @{
|
||||
run_dir = $runDir
|
||||
worktree = $worktreePath
|
||||
}
|
||||
Send-TelegramNotification `
|
||||
-EventName "run_completed" `
|
||||
-Title "Run completed" `
|
||||
-Message ("Implementer: " + $Implementer + "`nCodex verdict: " + $(if ($UseCodexMaster -and $null -ne $codexFinalVerdict) { [string]$codexFinalVerdict.verdict } else { "not-run" })) `
|
||||
-RunId $runId `
|
||||
-Stage "completed" `
|
||||
-Status "completed" | Out-Null
|
||||
Get-Content -Raw -Path (Join-Path $runDir "SUMMARY.md")
|
||||
}
|
||||
catch {
|
||||
Stop-HeartbeatMonitor
|
||||
$failureMessage = $_.Exception.Message
|
||||
if ($UseCodexMaster -and $null -eq $codexFinalVerdict) {
|
||||
try {
|
||||
$failureSections = @(
|
||||
"## FAILURE CONTEXT`nThe autopilot run failed before final acceptance.",
|
||||
"## FAILURE MESSAGE`n$failureMessage",
|
||||
"## IMPLEMENTER WORKTREE`n$worktreePath",
|
||||
"## IMPLEMENTER DIFF FILE`n$(Join-Path $runDir 'implementer.patch')",
|
||||
"## IMPLEMENTER STATUS FILE`n$(Join-Path $runDir 'implementer_status.txt')",
|
||||
"## REVIEW FILES`n$($reviewOutputs -join "`n")"
|
||||
)
|
||||
$null = Invoke-CodexReviewPass `
|
||||
-StageName "codex_review_failure" `
|
||||
-PromptFileName "codex_master_review_failure.md" `
|
||||
-OutputFileName "codex_master_failure.json" `
|
||||
-RunningMessage "Codex master failure review started" `
|
||||
-CompletedMessage "Codex master failure review completed" `
|
||||
-ExtraSections $failureSections
|
||||
}
|
||||
catch {
|
||||
$script:runState.errors += ("Codex failure review error: " + $_.Exception.Message)
|
||||
Add-RalphEvent -RunId $runId -Stage "codex_review_failure" -Status "failed" -Actor "codex_master" -Message $_.Exception.Message
|
||||
}
|
||||
}
|
||||
|
||||
$script:runState.status = "failed"
|
||||
$script:runState.stage = "failed"
|
||||
$script:runState.finished_at = (Get-Date).ToString("o")
|
||||
$script:runState.latest_message = $failureMessage
|
||||
$script:runState.errors += $failureMessage
|
||||
Save-RunState
|
||||
Add-RalphEvent -RunId $runId -Stage "failed" -Status "failed" -Message $failureMessage
|
||||
Send-TelegramNotification `
|
||||
-EventName "run_failed" `
|
||||
-Title "Run failed" `
|
||||
-Message $failureMessage `
|
||||
-RunId $runId `
|
||||
-Stage "failed" `
|
||||
-Status "failed" | Out-Null
|
||||
try {
|
||||
$autoFollowupResult = Invoke-AutoFollowupTask -OutcomeStatus "failed" -OutcomeSummary $failureMessage
|
||||
}
|
||||
catch {
|
||||
$script:runState.errors += ("Auto-followup generation error: " + $_.Exception.Message)
|
||||
Save-RunState
|
||||
Add-RalphEvent -RunId $runId -Stage "auto_followup" -Status "failed" -Actor "codex_master" -Message $_.Exception.Message
|
||||
}
|
||||
$summaryLines += @(
|
||||
"",
|
||||
"## Failure",
|
||||
"",
|
||||
$failureMessage
|
||||
)
|
||||
if ($null -ne $autoFollowupResult) {
|
||||
$summaryLines += @(
|
||||
"",
|
||||
"## Auto Followup",
|
||||
"",
|
||||
"- Title: $($autoFollowupResult.title)",
|
||||
"- Task directory: $($autoFollowupResult.task_directory)",
|
||||
"- Source markdown: $($autoFollowupResult.source_markdown)"
|
||||
)
|
||||
}
|
||||
Write-Utf8File -Path (Join-Path $runDir "SUMMARY.md") -Content (($summaryLines -join "`n") + "`n")
|
||||
throw
|
||||
}
|
||||
68
ralph/scripts/Start-RalphBackground.ps1
Normal file
68
ralph/scripts/Start-RalphBackground.ps1
Normal file
@@ -0,0 +1,68 @@
|
||||
param(
|
||||
[string]$TaskDirectory = "",
|
||||
[string]$RunLabel = "background",
|
||||
[string]$Implementer = "",
|
||||
[string[]]$Reviewers = @(),
|
||||
[switch]$DisableCodexMaster,
|
||||
[switch]$DisableAutoFix
|
||||
)
|
||||
|
||||
. (Join-Path $PSScriptRoot "Common.ps1")
|
||||
|
||||
$ralphRoot = Get-RalphRoot
|
||||
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
||||
$stdoutLog = Join-Path $ralphRoot ("logs\ralph-" + $timestamp + ".out.log")
|
||||
$stderrLog = Join-Path $ralphRoot ("logs\ralph-" + $timestamp + ".err.log")
|
||||
$stateFile = Join-Path $ralphRoot "state\last_background_run.json"
|
||||
|
||||
$autopilotScript = Join-Path $PSScriptRoot "Start-RalphAutopilot.ps1"
|
||||
$argumentParts = @(
|
||||
"-ExecutionPolicy Bypass",
|
||||
('-File "{0}"' -f $autopilotScript),
|
||||
('-RunLabel "{0}"' -f $RunLabel)
|
||||
)
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($TaskDirectory)) {
|
||||
$argumentParts += ('-TaskDirectory "{0}"' -f $TaskDirectory)
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($Implementer)) {
|
||||
$argumentParts += ('-Implementer "{0}"' -f $Implementer)
|
||||
}
|
||||
foreach ($reviewer in $Reviewers) {
|
||||
$argumentParts += ('-Reviewers "{0}"' -f $reviewer)
|
||||
}
|
||||
if ($DisableCodexMaster) {
|
||||
$argumentParts += '-UseCodexMaster:$false'
|
||||
}
|
||||
if ($DisableAutoFix) {
|
||||
$argumentParts += '-AutoFix:$false'
|
||||
}
|
||||
|
||||
$argumentList = $argumentParts -join ' '
|
||||
|
||||
$process = Start-Process `
|
||||
-FilePath "powershell.exe" `
|
||||
-ArgumentList $argumentList `
|
||||
-WorkingDirectory (Get-RepoRoot) `
|
||||
-WindowStyle Hidden `
|
||||
-RedirectStandardOutput $stdoutLog `
|
||||
-RedirectStandardError $stderrLog `
|
||||
-PassThru
|
||||
|
||||
$state = [ordered]@{
|
||||
pid = $process.Id
|
||||
started_at = (Get-Date).ToString("o")
|
||||
stdout_log = $stdoutLog
|
||||
stderr_log = $stderrLog
|
||||
run_label = $RunLabel
|
||||
status = "started"
|
||||
}
|
||||
|
||||
Write-JsonFile -Path $stateFile -Object $state
|
||||
Add-RalphEvent -RunId ("background-" + $timestamp) -Stage "background" -Status "started" -Actor "launcher" -Message "Background Ralph process launched" -Data @{
|
||||
pid = $process.Id
|
||||
run_label = $RunLabel
|
||||
stdout_log = $stdoutLog
|
||||
stderr_log = $stderrLog
|
||||
}
|
||||
Get-Content -Raw -Path $stateFile
|
||||
13
ralph/scripts/Start-RalphDashboard.ps1
Normal file
13
ralph/scripts/Start-RalphDashboard.ps1
Normal file
@@ -0,0 +1,13 @@
|
||||
param(
|
||||
[string]$Host = "127.0.0.1",
|
||||
[int]$Port = 8765
|
||||
)
|
||||
|
||||
. (Join-Path $PSScriptRoot "Common.ps1")
|
||||
|
||||
$dashboardScript = Join-Path (Get-RalphRoot) "gui\app.py"
|
||||
if (-not (Test-Path $dashboardScript)) {
|
||||
throw "Dashboard script not found: $dashboardScript"
|
||||
}
|
||||
|
||||
& python $dashboardScript --host $Host --port $Port
|
||||
69
ralph/scripts/Start-RalphInboxBackground.ps1
Normal file
69
ralph/scripts/Start-RalphInboxBackground.ps1
Normal file
@@ -0,0 +1,69 @@
|
||||
param(
|
||||
[int]$PollSeconds = 15,
|
||||
[string]$Implementer = "",
|
||||
[string[]]$Reviewers = @(),
|
||||
[switch]$DisableCodexMaster,
|
||||
[switch]$DisableAutoFix,
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
. (Join-Path $PSScriptRoot "Common.ps1")
|
||||
|
||||
$ralphRoot = Get-RalphRoot
|
||||
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
||||
$stdoutLog = Join-Path $ralphRoot ("logs\ralph-inbox-" + $timestamp + ".out.log")
|
||||
$stderrLog = Join-Path $ralphRoot ("logs\ralph-inbox-" + $timestamp + ".err.log")
|
||||
$stateFile = Join-Path $ralphRoot "state\last_inbox_background.json"
|
||||
$daemonScript = Join-Path $PSScriptRoot "Start-RalphInboxDaemon.ps1"
|
||||
|
||||
$argumentParts = @(
|
||||
"-ExecutionPolicy Bypass",
|
||||
('-File "{0}"' -f $daemonScript),
|
||||
('-PollSeconds {0}' -f $PollSeconds)
|
||||
)
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($Implementer)) {
|
||||
$argumentParts += ('-Implementer "{0}"' -f $Implementer)
|
||||
}
|
||||
foreach ($reviewer in $Reviewers) {
|
||||
$argumentParts += ('-Reviewers "{0}"' -f $reviewer)
|
||||
}
|
||||
if ($DisableCodexMaster) {
|
||||
$argumentParts += '-DisableCodexMaster'
|
||||
}
|
||||
if ($DisableAutoFix) {
|
||||
$argumentParts += '-DisableAutoFix'
|
||||
}
|
||||
if ($DryRun) {
|
||||
$argumentParts += '-DryRun'
|
||||
}
|
||||
|
||||
$argumentList = $argumentParts -join ' '
|
||||
|
||||
$process = Start-Process `
|
||||
-FilePath "powershell.exe" `
|
||||
-ArgumentList $argumentList `
|
||||
-WorkingDirectory (Get-RepoRoot) `
|
||||
-WindowStyle Hidden `
|
||||
-RedirectStandardOutput $stdoutLog `
|
||||
-RedirectStandardError $stderrLog `
|
||||
-PassThru
|
||||
|
||||
$state = [ordered]@{
|
||||
pid = $process.Id
|
||||
started_at = (Get-Date).ToString("o")
|
||||
stdout_log = $stdoutLog
|
||||
stderr_log = $stderrLog
|
||||
poll_seconds = $PollSeconds
|
||||
dry_run = [bool]$DryRun
|
||||
status = "started"
|
||||
}
|
||||
|
||||
Write-JsonFile -Path $stateFile -Object $state
|
||||
Add-RalphEvent -RunId ("inbox-background-" + $timestamp) -Stage "background" -Status "started" -Actor "launcher" -Message "Background Ralph inbox daemon launched" -Data @{
|
||||
pid = $process.Id
|
||||
stdout_log = $stdoutLog
|
||||
stderr_log = $stderrLog
|
||||
}
|
||||
|
||||
Get-Content -Raw -Path $stateFile
|
||||
292
ralph/scripts/Start-RalphInboxDaemon.ps1
Normal file
292
ralph/scripts/Start-RalphInboxDaemon.ps1
Normal file
@@ -0,0 +1,292 @@
|
||||
param(
|
||||
[int]$PollSeconds = 15,
|
||||
[string]$Implementer = "",
|
||||
[string[]]$Reviewers = @(),
|
||||
[switch]$DisableCodexMaster,
|
||||
[switch]$DisableAutoFix,
|
||||
[switch]$DryRun,
|
||||
[switch]$Once,
|
||||
[int]$MaxTasks = 0,
|
||||
[string]$InboxDirectory = ""
|
||||
)
|
||||
|
||||
. (Join-Path $PSScriptRoot "Common.ps1")
|
||||
|
||||
$roots = Get-RalphTaskRoots
|
||||
foreach ($path in $roots.Values) {
|
||||
Ensure-Directory -Path $path
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($InboxDirectory)) {
|
||||
$InboxDirectory = $roots.Inbox
|
||||
}
|
||||
Ensure-Directory -Path $InboxDirectory
|
||||
|
||||
$lockFile = Get-RalphStateFile -Name "inbox_daemon.lock.json"
|
||||
$daemonStateFile = Get-RalphStateFile -Name "inbox_daemon_state.json"
|
||||
$script:daemonRunId = "daemon-" + (Get-Date -Format "yyyyMMdd-HHmmss")
|
||||
$script:processedCount = 0
|
||||
|
||||
function Save-DaemonState {
|
||||
param(
|
||||
[string]$Status,
|
||||
[string]$Message,
|
||||
[hashtable]$Extra = @{}
|
||||
)
|
||||
|
||||
$state = [ordered]@{
|
||||
pid = $PID
|
||||
status = $Status
|
||||
message = $Message
|
||||
updated_at = (Get-Date).ToString("o")
|
||||
poll_seconds = $PollSeconds
|
||||
dry_run = [bool]$DryRun
|
||||
inbox_directory = $InboxDirectory
|
||||
processed_count = $script:processedCount
|
||||
}
|
||||
foreach ($key in $Extra.Keys) {
|
||||
$state[$key] = $Extra[$key]
|
||||
}
|
||||
Write-JsonFile -Path $daemonStateFile -Object $state
|
||||
}
|
||||
|
||||
function Acquire-DaemonLock {
|
||||
if (Test-Path $lockFile) {
|
||||
try {
|
||||
$existing = Read-JsonFile -Path $lockFile
|
||||
$existingPid = 0
|
||||
try { $existingPid = [int]$existing.pid } catch { $existingPid = 0 }
|
||||
if ($existingPid -gt 0) {
|
||||
$proc = Get-Process -Id $existingPid -ErrorAction SilentlyContinue
|
||||
if ($null -ne $proc) {
|
||||
throw "Ralph inbox daemon already running with PID $existingPid."
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
# stale or malformed lock; overwrite it
|
||||
}
|
||||
}
|
||||
|
||||
Write-JsonFile -Path $lockFile -Object ([ordered]@{
|
||||
pid = $PID
|
||||
started_at = (Get-Date).ToString("o")
|
||||
inbox_directory = $InboxDirectory
|
||||
})
|
||||
}
|
||||
|
||||
function Release-DaemonLock {
|
||||
if (Test-Path $lockFile) {
|
||||
Remove-Item -LiteralPath $lockFile -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
function Get-NextInboxItem {
|
||||
$directories = @(Get-ChildItem -LiteralPath $InboxDirectory -Directory -ErrorAction SilentlyContinue | Sort-Object LastWriteTime, Name)
|
||||
foreach ($dir in $directories) {
|
||||
if (Test-Path (Join-Path $dir.FullName "TASK.md")) {
|
||||
return [ordered]@{
|
||||
kind = "taskpack"
|
||||
path = $dir.FullName
|
||||
name = $dir.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$files = @(Get-ChildItem -LiteralPath $InboxDirectory -File -Filter *.md -ErrorAction SilentlyContinue | Sort-Object LastWriteTime, Name)
|
||||
foreach ($file in $files) {
|
||||
return [ordered]@{
|
||||
kind = "markdown"
|
||||
path = $file.FullName
|
||||
name = $file.Name
|
||||
}
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Move-InboxItemToProcessing {
|
||||
param([hashtable]$Item)
|
||||
|
||||
$id = "{0}-{1}" -f (Get-Date -Format "yyyyMMdd-HHmmss"), (Convert-ToRalphSlug -Text ([System.IO.Path]::GetFileNameWithoutExtension($Item.name)))
|
||||
$targetDir = Join-Path $roots.Processing $id
|
||||
Ensure-Directory -Path $targetDir
|
||||
|
||||
if ($Item.kind -eq "taskpack") {
|
||||
$targetParent = Split-Path -Parent $targetDir
|
||||
if (-not (Test-Path $targetParent)) {
|
||||
Ensure-Directory -Path $targetParent
|
||||
}
|
||||
Remove-Item -LiteralPath $targetDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Move-Item -LiteralPath $Item.path -Destination $targetDir
|
||||
return [ordered]@{
|
||||
id = $id
|
||||
task_directory = $targetDir
|
||||
title = $Item.name
|
||||
source = $Item.path
|
||||
}
|
||||
}
|
||||
|
||||
$taskInfo = New-RalphTaskPackFromMarkdown -MarkdownPath $Item.path -TaskId $id -TargetDirectory $targetDir
|
||||
Remove-Item -LiteralPath $Item.path -Force
|
||||
return [ordered]@{
|
||||
id = $taskInfo.id
|
||||
task_directory = $taskInfo.task_directory
|
||||
title = $taskInfo.title
|
||||
source = $Item.path
|
||||
}
|
||||
}
|
||||
|
||||
function Finalize-ProcessedItem {
|
||||
param(
|
||||
[hashtable]$Processed,
|
||||
[string]$DestinationRoot,
|
||||
[string]$State,
|
||||
[string]$RunId = "",
|
||||
[string]$Summary = ""
|
||||
)
|
||||
|
||||
Ensure-Directory -Path $DestinationRoot
|
||||
$destination = Join-Path $DestinationRoot ([System.IO.Path]::GetFileName($Processed.task_directory))
|
||||
if (Test-Path $destination) {
|
||||
Remove-Item -LiteralPath $destination -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
Move-Item -LiteralPath $Processed.task_directory -Destination $destination
|
||||
|
||||
$submissionFile = Join-Path $destination "submission.json"
|
||||
$submission = [ordered]@{
|
||||
id = $Processed.id
|
||||
title = $Processed.title
|
||||
state = $State
|
||||
finished_at = (Get-Date).ToString("o")
|
||||
source = $Processed.source
|
||||
run_id = $RunId
|
||||
summary = $Summary
|
||||
}
|
||||
Write-JsonFile -Path $submissionFile -Object $submission
|
||||
return $destination
|
||||
}
|
||||
|
||||
Acquire-DaemonLock
|
||||
Save-DaemonState -Status "running" -Message "Inbox daemon started"
|
||||
Add-RalphEvent -RunId $script:daemonRunId -Stage "daemon" -Status "running" -Actor "daemon" -Message "Ralph inbox daemon started" -Data @{
|
||||
inbox_directory = $InboxDirectory
|
||||
}
|
||||
Send-TelegramNotification `
|
||||
-EventName "daemon_started" `
|
||||
-Title "Ralph daemon started" `
|
||||
-Message ("Inbox: " + $InboxDirectory) `
|
||||
-RunId $script:daemonRunId `
|
||||
-Stage "daemon" `
|
||||
-Status "running" | Out-Null
|
||||
|
||||
try {
|
||||
while ($true) {
|
||||
$item = Get-NextInboxItem
|
||||
if ($null -eq $item) {
|
||||
if ($Once) {
|
||||
Save-DaemonState -Status "idle" -Message "No tasks in inbox; exiting because -Once was used"
|
||||
break
|
||||
}
|
||||
Save-DaemonState -Status "idle" -Message "Waiting for inbox tasks"
|
||||
Start-Sleep -Seconds $PollSeconds
|
||||
continue
|
||||
}
|
||||
|
||||
$processed = Move-InboxItemToProcessing -Item $item
|
||||
$script:processedCount += 1
|
||||
Save-DaemonState -Status "running" -Message ("Processing task " + $processed.id) -Extra @{
|
||||
current_task = $processed.id
|
||||
current_task_directory = $processed.task_directory
|
||||
}
|
||||
Add-RalphEvent -RunId $script:daemonRunId -Stage "queue" -Status "started" -Actor "daemon" -Message ("Dequeued task " + $processed.id) -Data @{
|
||||
task_directory = $processed.task_directory
|
||||
}
|
||||
Send-TelegramNotification `
|
||||
-EventName "task_processing" `
|
||||
-Title "Task processing" `
|
||||
-Message ($processed.title + "`n" + $processed.task_directory) `
|
||||
-RunId $processed.id `
|
||||
-Stage "queue" `
|
||||
-Status "started" | Out-Null
|
||||
|
||||
$runId = ""
|
||||
$summary = ""
|
||||
try {
|
||||
$args = @(
|
||||
"-ExecutionPolicy", "Bypass",
|
||||
"-File", (Join-Path $PSScriptRoot "Start-RalphAutopilot.ps1"),
|
||||
"-TaskDirectory", $processed.task_directory,
|
||||
"-RunLabel", "queue"
|
||||
)
|
||||
if ($DryRun) {
|
||||
$args += "-DryRun"
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($Implementer)) {
|
||||
$args += @("-Implementer", $Implementer)
|
||||
}
|
||||
foreach ($reviewer in $Reviewers) {
|
||||
$args += @("-Reviewers", $reviewer)
|
||||
}
|
||||
if ($DisableCodexMaster) {
|
||||
$args += "-UseCodexMaster:$false"
|
||||
}
|
||||
if ($DisableAutoFix) {
|
||||
$args += "-AutoFix:$false"
|
||||
}
|
||||
|
||||
& powershell.exe @args
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Start-RalphAutopilot.ps1 failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
|
||||
$currentRunState = Read-JsonFile -Path (Get-RalphStateFile -Name "current_run.json")
|
||||
$runId = [string]$currentRunState.run_id
|
||||
$summary = [string]$currentRunState.latest_message
|
||||
$finalPath = Finalize-ProcessedItem -Processed $processed -DestinationRoot $roots.Completed -State "completed" -RunId $runId -Summary $summary
|
||||
Add-RalphEvent -RunId $script:daemonRunId -Stage "queue" -Status "completed" -Actor "daemon" -Message ("Completed task " + $processed.id) -Data @{
|
||||
task_directory = $finalPath
|
||||
run_id = $runId
|
||||
}
|
||||
Send-TelegramNotification `
|
||||
-EventName "task_completed" `
|
||||
-Title "Task completed" `
|
||||
-Message ($processed.title + "`n" + $summary) `
|
||||
-RunId $runId `
|
||||
-Stage "queue" `
|
||||
-Status "completed" | Out-Null
|
||||
}
|
||||
catch {
|
||||
$summary = $_.Exception.Message
|
||||
$failedPath = Finalize-ProcessedItem -Processed $processed -DestinationRoot $roots.Failed -State "failed" -RunId $runId -Summary $summary
|
||||
Add-RalphEvent -RunId $script:daemonRunId -Stage "queue" -Status "failed" -Actor "daemon" -Message ("Failed task " + $processed.id + ": " + $summary) -Data @{
|
||||
task_directory = $failedPath
|
||||
run_id = $runId
|
||||
}
|
||||
Send-TelegramNotification `
|
||||
-EventName "task_failed" `
|
||||
-Title "Task failed" `
|
||||
-Message ($processed.title + "`n" + $summary) `
|
||||
-RunId $runId `
|
||||
-Stage "queue" `
|
||||
-Status "failed" | Out-Null
|
||||
}
|
||||
|
||||
if ($MaxTasks -gt 0 -and $script:processedCount -ge $MaxTasks) {
|
||||
Save-DaemonState -Status "completed" -Message "MaxTasks limit reached"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Save-DaemonState -Status "stopped" -Message "Inbox daemon stopped"
|
||||
Add-RalphEvent -RunId $script:daemonRunId -Stage "daemon" -Status "stopped" -Actor "daemon" -Message "Ralph inbox daemon stopped"
|
||||
Send-TelegramNotification `
|
||||
-EventName "daemon_stopped" `
|
||||
-Title "Ralph daemon stopped" `
|
||||
-Message "Inbox daemon stopped" `
|
||||
-RunId $script:daemonRunId `
|
||||
-Stage "daemon" `
|
||||
-Status "stopped" | Out-Null
|
||||
Release-DaemonLock
|
||||
}
|
||||
54
ralph/scripts/Stop-RalphInboxDaemon.ps1
Normal file
54
ralph/scripts/Stop-RalphInboxDaemon.ps1
Normal file
@@ -0,0 +1,54 @@
|
||||
. (Join-Path $PSScriptRoot "Common.ps1")
|
||||
|
||||
$lockFile = Get-RalphStateFile -Name "inbox_daemon.lock.json"
|
||||
$daemonStateFile = Get-RalphStateFile -Name "inbox_daemon_state.json"
|
||||
|
||||
if (-not (Test-Path $lockFile)) {
|
||||
@{
|
||||
stopped = $false
|
||||
message = "No inbox daemon lock file found."
|
||||
} | ConvertTo-Json -Depth 20
|
||||
return
|
||||
}
|
||||
|
||||
$lock = Read-JsonFile -Path $lockFile
|
||||
$daemonPid = 0
|
||||
try { $daemonPid = [int]$lock.pid } catch { $daemonPid = 0 }
|
||||
|
||||
if ($daemonPid -le 0) {
|
||||
Remove-Item -LiteralPath $lockFile -Force -ErrorAction SilentlyContinue
|
||||
@{
|
||||
stopped = $false
|
||||
message = "Lock file was invalid and has been removed."
|
||||
} | ConvertTo-Json -Depth 20
|
||||
return
|
||||
}
|
||||
|
||||
$proc = Get-Process -Id $daemonPid -ErrorAction SilentlyContinue
|
||||
if ($null -ne $proc) {
|
||||
Stop-Process -Id $daemonPid -Force
|
||||
}
|
||||
|
||||
Remove-Item -LiteralPath $lockFile -Force -ErrorAction SilentlyContinue
|
||||
Write-JsonFile -Path $daemonStateFile -Object ([ordered]@{
|
||||
pid = $daemonPid
|
||||
status = "stopped"
|
||||
message = "Inbox daemon stopped manually."
|
||||
updated_at = (Get-Date).ToString("o")
|
||||
})
|
||||
|
||||
Add-RalphEvent -RunId ("inbox-daemon-stop-" + (Get-Date -Format "yyyyMMdd-HHmmss")) -Stage "daemon" -Status "stopped" -Actor "stop" -Message "Inbox daemon stopped manually" -Data @{
|
||||
pid = $daemonPid
|
||||
}
|
||||
Send-TelegramNotification `
|
||||
-EventName "daemon_stopped" `
|
||||
-Title "Ralph daemon stopped manually" `
|
||||
-Message ("PID " + $daemonPid) `
|
||||
-RunId ("inbox-daemon-stop-" + (Get-Date -Format "yyyyMMdd-HHmmss")) `
|
||||
-Stage "daemon" `
|
||||
-Status "stopped" | Out-Null
|
||||
|
||||
@{
|
||||
stopped = $true
|
||||
pid = $daemonPid
|
||||
} | ConvertTo-Json -Depth 20
|
||||
61
ralph/scripts/Submit-RalphTask.ps1
Normal file
61
ralph/scripts/Submit-RalphTask.ps1
Normal file
@@ -0,0 +1,61 @@
|
||||
param(
|
||||
[string]$SourceFile = "",
|
||||
[string]$Text = "",
|
||||
[string]$Title = "",
|
||||
[switch]$PassThru
|
||||
)
|
||||
|
||||
. (Join-Path $PSScriptRoot "Common.ps1")
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($SourceFile) -and [string]::IsNullOrWhiteSpace($Text)) {
|
||||
throw "Provide either -SourceFile or -Text."
|
||||
}
|
||||
|
||||
$roots = Get-RalphTaskRoots
|
||||
foreach ($path in $roots.Values) {
|
||||
Ensure-Directory -Path $path
|
||||
}
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($SourceFile)) {
|
||||
$taskInfo = New-RalphTaskPackFromMarkdown -MarkdownPath $SourceFile -Title $Title
|
||||
}
|
||||
else {
|
||||
$tempName = "{0}.md" -f ([guid]::NewGuid().ToString("N"))
|
||||
$tempPath = Join-Path ([System.IO.Path]::GetTempPath()) $tempName
|
||||
try {
|
||||
Write-Utf8File -Path $tempPath -Content ($Text.Trim() + "`n")
|
||||
$taskInfo = New-RalphTaskPackFromMarkdown -MarkdownPath $tempPath -Title $Title
|
||||
}
|
||||
finally {
|
||||
if (Test-Path $tempPath) {
|
||||
Remove-Item -LiteralPath $tempPath -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Add-RalphEvent -RunId $taskInfo.id -Stage "submit" -Status "queued" -Actor "submit" -Message ("Task queued: " + $taskInfo.title) -Data @{
|
||||
task_directory = $taskInfo.task_directory
|
||||
}
|
||||
Send-TelegramNotification `
|
||||
-EventName "task_queued" `
|
||||
-Title "Task queued" `
|
||||
-Message ($taskInfo.title + "`n" + $taskInfo.task_directory) `
|
||||
-RunId $taskInfo.id `
|
||||
-Stage "submit" `
|
||||
-Status "queued" | Out-Null
|
||||
|
||||
$result = [ordered]@{
|
||||
id = $taskInfo.id
|
||||
title = $taskInfo.title
|
||||
state = "queued"
|
||||
task_directory = $taskInfo.task_directory
|
||||
task_file = $taskInfo.task_file
|
||||
}
|
||||
|
||||
$json = ($result | ConvertTo-Json -Depth 20)
|
||||
if ($PassThru) {
|
||||
$result
|
||||
}
|
||||
else {
|
||||
$json
|
||||
}
|
||||
80
ralph/scripts/Test-RalphCodex.ps1
Normal file
80
ralph/scripts/Test-RalphCodex.ps1
Normal file
@@ -0,0 +1,80 @@
|
||||
param(
|
||||
[switch]$Full
|
||||
)
|
||||
|
||||
. (Join-Path $PSScriptRoot "Common.ps1")
|
||||
|
||||
$codexConfig = Get-CodexConfig
|
||||
$preferredCmd = Join-Path $env:APPDATA "npm\codex.cmd"
|
||||
$preferredPs1 = Join-Path $env:APPDATA "npm\codex.ps1"
|
||||
$codexPath = $null
|
||||
if (Test-Path $preferredCmd) {
|
||||
$codexPath = $preferredCmd
|
||||
}
|
||||
elseif (Test-Path $preferredPs1) {
|
||||
$codexPath = $preferredPs1
|
||||
}
|
||||
else {
|
||||
$codexCommand = Get-Command codex -ErrorAction SilentlyContinue
|
||||
if ($null -ne $codexCommand) {
|
||||
$codexPath = $codexCommand.Source
|
||||
}
|
||||
}
|
||||
|
||||
$result = [ordered]@{
|
||||
ok = $false
|
||||
binary_found = $false
|
||||
binary_path = $codexPath
|
||||
session_id = [string]$codexConfig.session_id
|
||||
model = [string]$codexConfig.model
|
||||
help_ok = $false
|
||||
full_ok = $false
|
||||
output = ""
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($codexPath)) {
|
||||
$result.output = "codex executable not found"
|
||||
}
|
||||
else {
|
||||
$result.binary_found = $true
|
||||
$helpOutput = & $codexPath exec resume --help 2>&1
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
$result.help_ok = $true
|
||||
}
|
||||
$result.output = (@($helpOutput) -join [Environment]::NewLine)
|
||||
|
||||
if ($Full) {
|
||||
$tempPromptPath = Join-Path ([System.IO.Path]::GetTempPath()) ("ralph-codex-smoke-" + [guid]::NewGuid().ToString("N") + ".md")
|
||||
$tempOutput = Join-Path ([System.IO.Path]::GetTempPath()) ("ralph-codex-smoke-" + [guid]::NewGuid().ToString("N") + ".json")
|
||||
$tempPrompt = @'
|
||||
You are Codex.
|
||||
Return exactly one JSON object and nothing else.
|
||||
Use this exact payload:
|
||||
{"verdict":"pass","acceptance_passed":true,"fix_required":false,"summary":"smoke","highest_risk_issues":[],"next_sprint_needed":false,"next_sprint_brief":""}
|
||||
'@
|
||||
try {
|
||||
Write-Utf8File -Path $tempPromptPath -Content ($tempPrompt.Trim() + "`n")
|
||||
$verdict = & (Join-Path $PSScriptRoot "Invoke-CodexMaster.ps1") -PromptFile $tempPromptPath -OutputFile $tempOutput
|
||||
$result.output = ($result.output + [Environment]::NewLine + ($verdict | ConvertTo-Json -Depth 20)).Trim()
|
||||
if ($null -ne $verdict -and [string]$verdict.verdict -eq "pass" -and [bool]$verdict.acceptance_passed) {
|
||||
$result.full_ok = $true
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$result.output = ($result.output + [Environment]::NewLine + $_.Exception.Message).Trim()
|
||||
}
|
||||
finally {
|
||||
if (Test-Path $tempPromptPath) {
|
||||
Remove-Item -LiteralPath $tempPromptPath -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
if (Test-Path $tempOutput) {
|
||||
Remove-Item -LiteralPath $tempOutput -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$result.ok = [bool]($result.binary_found -and $result.help_ok -and ((-not $Full) -or $result.full_ok))
|
||||
$outputPath = Join-Path (Get-RalphRoot) "state\codex_smoke.json"
|
||||
Write-Utf8File -Path $outputPath -Content ((@($result) | ConvertTo-Json -Depth 20) + "`n")
|
||||
Get-Content -Raw -Path $outputPath
|
||||
105
ralph/scripts/Test-RalphProviders.ps1
Normal file
105
ralph/scripts/Test-RalphProviders.ps1
Normal file
@@ -0,0 +1,105 @@
|
||||
param(
|
||||
[string[]]$ProviderNames = @()
|
||||
)
|
||||
|
||||
. (Join-Path $PSScriptRoot "Common.ps1")
|
||||
|
||||
function Get-ResponseText {
|
||||
param($Response)
|
||||
|
||||
if ($null -eq $Response) {
|
||||
return ""
|
||||
}
|
||||
|
||||
if ($Response.content) {
|
||||
$textBlocks = @($Response.content | Where-Object { $_.type -eq "text" -and $_.text })
|
||||
if ($textBlocks.Count -gt 0) {
|
||||
return (($textBlocks | ForEach-Object { [string]$_.text }) -join " ").Trim()
|
||||
}
|
||||
|
||||
$fallbackBlocks = @($Response.content | Where-Object { $_.thinking -or $_.text })
|
||||
if ($fallbackBlocks.Count -gt 0) {
|
||||
$parts = foreach ($block in $fallbackBlocks) {
|
||||
if ($block.text) { [string]$block.text }
|
||||
elseif ($block.thinking) { [string]$block.thinking }
|
||||
}
|
||||
return (($parts | Where-Object { $_ }) -join " ").Trim()
|
||||
}
|
||||
}
|
||||
|
||||
return (($Response | ConvertTo-Json -Depth 20) -replace "\s+", " ").Trim()
|
||||
}
|
||||
|
||||
$config = Get-ProvidersConfig
|
||||
if ($ProviderNames.Count -eq 0) {
|
||||
$ProviderNames = @($config.providers.PSObject.Properties.Name)
|
||||
}
|
||||
else {
|
||||
$expanded = @()
|
||||
foreach ($entry in $ProviderNames) {
|
||||
if ($null -ne $entry) {
|
||||
$expanded += ([string]$entry).Split(",") | ForEach-Object { $_.Trim() } | Where-Object { $_ }
|
||||
}
|
||||
}
|
||||
$ProviderNames = $expanded
|
||||
}
|
||||
|
||||
$results = @()
|
||||
|
||||
foreach ($name in $ProviderNames) {
|
||||
$provider = Get-ProviderConfig -Name $name
|
||||
$endpoint = ([string]$provider.base_url).TrimEnd("/") + "/v1/messages"
|
||||
|
||||
$headers = @{
|
||||
"x-api-key" = [string]$provider.auth_token
|
||||
"anthropic-version" = "2023-06-01"
|
||||
"content-type" = "application/json"
|
||||
}
|
||||
|
||||
$body = @{
|
||||
model = [string]$provider.model
|
||||
max_tokens = 16
|
||||
messages = @(
|
||||
@{
|
||||
role = "user"
|
||||
content = "Respond with exactly OK and nothing else."
|
||||
}
|
||||
)
|
||||
} | ConvertTo-Json -Depth 10
|
||||
|
||||
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
|
||||
try {
|
||||
$response = Invoke-RestMethod -Method Post -Uri $endpoint -Headers $headers -Body $body -TimeoutSec 120
|
||||
$stopwatch.Stop()
|
||||
$text = Get-ResponseText -Response $response
|
||||
$semanticOk = ($text.Trim() -eq "OK")
|
||||
|
||||
$results += [ordered]@{
|
||||
provider = $name
|
||||
model = [string]$provider.model
|
||||
endpoint = $endpoint
|
||||
http_ok = $true
|
||||
semantic_ok = $semanticOk
|
||||
ok = $semanticOk
|
||||
latency_ms = $stopwatch.ElapsedMilliseconds
|
||||
response = $text.Trim()
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$stopwatch.Stop()
|
||||
$results += [ordered]@{
|
||||
provider = $name
|
||||
model = [string]$provider.model
|
||||
endpoint = $endpoint
|
||||
http_ok = $false
|
||||
semantic_ok = $false
|
||||
ok = $false
|
||||
latency_ms = $stopwatch.ElapsedMilliseconds
|
||||
response = $_.Exception.Message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$outputPath = Join-Path (Get-RalphRoot) "state\provider_smoke.json"
|
||||
Write-Utf8File -Path $outputPath -Content ((@($results) | ConvertTo-Json -Depth 10) + "`n")
|
||||
Get-Content -Raw -Path $outputPath
|
||||
16
ralph/scripts/Test-RalphTelegram.ps1
Normal file
16
ralph/scripts/Test-RalphTelegram.ps1
Normal file
@@ -0,0 +1,16 @@
|
||||
param(
|
||||
[string]$Title = "Ralph Telegram test",
|
||||
[string]$Message = "Telegram notifications are enabled."
|
||||
)
|
||||
|
||||
. (Join-Path $PSScriptRoot "Common.ps1")
|
||||
|
||||
$result = Send-TelegramNotification `
|
||||
-EventName "run_started" `
|
||||
-Title $Title `
|
||||
-Message $Message `
|
||||
-RunId ("telegram-test-" + (Get-Date -Format "yyyyMMdd-HHmmss")) `
|
||||
-Stage "test" `
|
||||
-Status "manual"
|
||||
|
||||
($result | ConvertTo-Json -Depth 20)
|
||||
18
ralph/scripts/Uninstall-RalphScheduledTask.ps1
Normal file
18
ralph/scripts/Uninstall-RalphScheduledTask.ps1
Normal file
@@ -0,0 +1,18 @@
|
||||
param(
|
||||
[string]$TaskName = "RalphInboxDaemon"
|
||||
)
|
||||
|
||||
try {
|
||||
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false -ErrorAction Stop
|
||||
@{
|
||||
task_name = $TaskName
|
||||
removed = $true
|
||||
} | ConvertTo-Json -Depth 20
|
||||
}
|
||||
catch {
|
||||
@{
|
||||
task_name = $TaskName
|
||||
removed = $false
|
||||
error = $_.Exception.Message
|
||||
} | ConvertTo-Json -Depth 20
|
||||
}
|
||||
1
ralph/state/.gitkeep
Normal file
1
ralph/state/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
ralph/tasks/completed/.gitkeep
Normal file
1
ralph/tasks/completed/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
- daemon exits cleanly
|
||||
- a completed task pack is produced
|
||||
- a run directory exists with SUMMARY.md
|
||||
@@ -0,0 +1,9 @@
|
||||
# Context
|
||||
|
||||
List the canonical docs and code paths the worker must read first.
|
||||
|
||||
- active sprint doc
|
||||
- current handoff
|
||||
- relevant source files
|
||||
- known evidence files
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# Ralph Dry Run Validation
|
||||
|
||||
## Goal
|
||||
Validate that the inbox daemon can convert a markdown task, process it in dry-run mode, and archive it as completed.
|
||||
|
||||
## Acceptance Criteria
|
||||
- daemon exits cleanly
|
||||
- a completed task pack is produced
|
||||
- a run directory exists with SUMMARY.md
|
||||
4
ralph/tasks/completed/20260403-181602-test-task/TASK.md
Normal file
4
ralph/tasks/completed/20260403-181602-test-task/TASK.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Ralph Dry Run Validation
|
||||
|
||||
## Goal
|
||||
Validate that the inbox daemon can convert a markdown task, process it in dry-run mode, and archive it as completed.
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"id": "20260403-181602-test-task",
|
||||
"title": "Ralph Dry Run Validation",
|
||||
"state": "completed",
|
||||
"finished_at": "2026-04-03T18:16:02.8812410-03:00",
|
||||
"source": "C:\\Users\\ren\\AppData\\Local\\Temp\\ralph-inbox-test-da9d628bc10b432da976cb4184661a1f\\TEST_TASK.md",
|
||||
"run_id": "20260403-181602-queue",
|
||||
"summary": "Dry run prepared successfully"
|
||||
}
|
||||
12
ralph/tasks/current/ACCEPTANCE.md
Normal file
12
ralph/tasks/current/ACCEPTANCE.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Acceptance
|
||||
|
||||
The task is only complete if all of these are true:
|
||||
|
||||
1. `kimi.md` exists in project root and is at least 200 lines
|
||||
2. File includes Ralph workflow section (how to behave as Implementer)
|
||||
3. File includes project architecture overview (MCP → Server → Remote Script)
|
||||
4. File includes critical file paths with absolute Windows paths
|
||||
5. File includes common commands (PowerShell syntax)
|
||||
6. File includes debugging checklist (what to check when things fail)
|
||||
7. File references existing KIMI_K2_*.md files appropriately
|
||||
8. CHANGES.md documents what was created and any gaps
|
||||
61
ralph/tasks/current/CHANGES.md
Normal file
61
ralph/tasks/current/CHANGES.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# CHANGES.md - Desarrollo de kimi.md
|
||||
|
||||
**Fecha**: 2026-03-30
|
||||
**Iteración**: Ralph Loop - Iteración 1
|
||||
**Tarea**: Crear guía completa kimi.md para Implementer en Ralph Wiggum
|
||||
|
||||
## Qué se hizo
|
||||
|
||||
### 1. Análisis de archivos existentes
|
||||
|
||||
Leídos todos los archivos de handoff Kimi para sintetizar información:
|
||||
- `KIMI_K2_START_HERE.md` (156 líneas) - orden de lectura y verdades del proyecto
|
||||
- `KIMI_K2_ACTIVE_HANDOFF.md` (324 líneas) - estado actual detallado
|
||||
- `KIMI_K2_BOOTSTRAP.md` (95 líneas) - orden de lectura extendido
|
||||
- `KIMI_K2_CODEBASE_FIXES.md` (400 líneas) - fixes históricos
|
||||
- `KIMI_K2_NOTE_API_FIX.md` (292 líneas) - fix específico de API de notas
|
||||
|
||||
### 2. Actualización de kimi.md
|
||||
|
||||
El archivo `kimi.md` ya existía (31KB, 674 líneas) como "Kimi Professional Iteration Playbook" con 400 tareas de iteración.
|
||||
|
||||
**Agregado al principio**: Sección completa de **Ralph Wiggum Workflow para Implementer** incluyendo:
|
||||
|
||||
- Flujo de trabajo del Implementer en Ralph
|
||||
- Reglas de Ralph (no auto-merge, worktree aislado, CHANGES.md obligatorio)
|
||||
- Scripts de Ralph disponibles
|
||||
- Configuración de providers
|
||||
- TL;DR para empezar rápido
|
||||
- Metadatos de rol/modelo/proyecto
|
||||
|
||||
## Estado del archivo
|
||||
|
||||
- **Tamaño**: ~32KB
|
||||
- **Líneas**: ~700
|
||||
- **Cobertura**:
|
||||
- ✅ Ralph workflow completo
|
||||
- ✅ 400 tareas de iteración profesional
|
||||
- ✅ Comandos PowerShell
|
||||
- ✅ Checklist de debugging
|
||||
- ✅ Referencias cruzadas a todos los KIMI_K2_*.md
|
||||
|
||||
## Qué queda por hacer
|
||||
|
||||
1. Validar que el archivo se cargue correctamente en futuras sesiones de Ralph
|
||||
2. Posiblemente agregar sección de "Mensajes entre agentes" cuando Ralph tenga peer DMs
|
||||
3. Documentar el formato exacto de eventos en `ralph/state/events.jsonl`
|
||||
|
||||
## Evidencia
|
||||
|
||||
```powershell
|
||||
# Verificar archivo actualizado
|
||||
Get-Content "C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\kimi.md" -Head 50
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
El archivo `kimi.md` ahora sirve como:
|
||||
1. **Playbook de iteración** para trabajo autónomo (400 tareas)
|
||||
2. **Handoff para Ralph** con instrucciones específicas de workflow
|
||||
|
||||
No se rompió contenido existente - solo se agregó contexto de Ralph al inicio.
|
||||
35
ralph/tasks/current/CONTEXT.md
Normal file
35
ralph/tasks/current/CONTEXT.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Context
|
||||
|
||||
## Ralph Role Context
|
||||
|
||||
You are the **Implementer** in a Ralph Wiggum loop. Your job is to:
|
||||
1. Read the TASK.md and understand what needs to be done
|
||||
2. Work in the assigned worktree (created by Ralph)
|
||||
3. Make concrete, evidence-backed changes
|
||||
4. Leave a CHANGES.md documenting what you did
|
||||
5. Do NOT merge to main - leave results in worktree for review
|
||||
|
||||
## Read These First
|
||||
|
||||
1. `CLAUDE.md` - canonical project context (highest priority)
|
||||
2. `KIMI_K2_START_HERE.md` - existing handoff for Kimi
|
||||
3. `KIMI_K2_ACTIVE_HANDOFF.md` - current state from Kimi's perspective
|
||||
4. `KIMI_K2_BOOTSTRAP.md` - bootstrap information
|
||||
5. `KIMI_K2_CODEBASE_FIXES.md` - known fixes
|
||||
6. `KIMI_K2_NOTE_API_FIX.md` - API-specific notes
|
||||
|
||||
## Project Structure (from CLAUDE.md)
|
||||
|
||||
- User-facing root: `C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts`
|
||||
- MCP code root: `AbletonMCP_AI\AbletonMCP_AI`
|
||||
- Active entrypoint: `abletonmcp_init.py`
|
||||
- Remote Script: `AbletonMCP_AI\__init__.py`
|
||||
- MCP Server: `AbletonMCP_AI\AbletonMCP_AI\MCP_Server\server.py`
|
||||
|
||||
## Key Constraints
|
||||
|
||||
- Always read files before modifying them
|
||||
- Use PowerShell syntax (Windows native)
|
||||
- Verify with logs and runtime evidence
|
||||
- Do not trust stale docs over live code
|
||||
- Keep each mutation small to avoid Audio queue timeout
|
||||
30
ralph/tasks/current/TASK.md
Normal file
30
ralph/tasks/current/TASK.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Task
|
||||
|
||||
## Goal
|
||||
|
||||
Create a comprehensive `kimi.md` file from scratch that serves as the canonical handoff guide for the Kimi K2.5 model when acting as the **Implementer** in Ralph Wiggum loops.
|
||||
|
||||
This file should be the single source of truth for Kimi to understand and work effectively in the AbletonMCP-AI project.
|
||||
|
||||
## Files in scope
|
||||
|
||||
- `kimi.md` (to be created in project root)
|
||||
- All existing KIMI_K2_*.md files for reference
|
||||
- `CLAUDE.md` (project canonical context)
|
||||
- `AbletonMCP_AI/AbletonMCP_AI/MCP_Server/` modules
|
||||
- `AbletonMCP_AI/__init__.py` and `Remote_Script.py`
|
||||
|
||||
## Constraints
|
||||
|
||||
- use Windows native PowerShell
|
||||
- read existing files before writing kimi.md
|
||||
- do not copy-paste blindly - synthesize and improve
|
||||
- keep sections actionable and concrete
|
||||
- include Ralph-specific workflow guidance
|
||||
- include common pitfalls and how to avoid them
|
||||
|
||||
## Expected output
|
||||
|
||||
- `kimi.md` in project root with complete handoff information
|
||||
- `CHANGES.md` in the run directory documenting what was created
|
||||
- clear indication of any gaps that need future work
|
||||
1
ralph/tasks/failed/.gitkeep
Normal file
1
ralph/tasks/failed/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
The sprint is complete only if all of these are true:
|
||||
|
||||
1. the open project `song.als` was used as the validation target
|
||||
2. MCP editing tools were validated live, not just compiled
|
||||
3. the open project received a real edit pass
|
||||
4. harmonic MIDI backbone exists in Arrangement and covers materially more of the song
|
||||
5. silence islands and mirrored symmetry were measured before and after
|
||||
6. the result is less empty and less repetitive without losing structure
|
||||
7. sound selection logic for aggressive snares became more selective instead of a blind blacklist
|
||||
8. all changed Python files compile
|
||||
9. relevant tests pass
|
||||
10. the report includes exact MCP calls used on the open project
|
||||
11. the report includes exact before/after coherence metrics
|
||||
12. Codex final verdict is `pass`
|
||||
@@ -0,0 +1,9 @@
|
||||
# Context
|
||||
|
||||
List the canonical docs and code paths the worker must read first.
|
||||
|
||||
- active sprint doc
|
||||
- current handoff
|
||||
- relevant source files
|
||||
- known evidence files
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
# SPRINT v0.1.41 - Ralph Swarm - Open Project Editing and Coherence
|
||||
|
||||
## Goal
|
||||
|
||||
Use the Ralph swarm to work on the already-open Ableton project:
|
||||
|
||||
- `C:\Users\ren\Desktop\song Project\song.als`
|
||||
|
||||
This is not a new-song generation sprint.
|
||||
|
||||
This sprint is about:
|
||||
|
||||
1. giving the MCP more real editing power over open projects
|
||||
2. using those tools on the current open project
|
||||
3. improving coherence, continuity and musical identity
|
||||
4. reducing symmetry, repeated blocks and empty gaps
|
||||
5. treating harmonic MIDI as a real backbone across the full arrangement
|
||||
|
||||
## Important Product Clarification
|
||||
|
||||
The user comes from FL Studio and may say `piano roll`.
|
||||
|
||||
In this project that means:
|
||||
|
||||
- harmonic MIDI backbone
|
||||
- long-form arrangement MIDI that carries harmony/melodic identity
|
||||
- editable MIDI clips in Arrangement
|
||||
|
||||
It does **not** mean:
|
||||
|
||||
- force piano timbre everywhere
|
||||
- spam piano loops
|
||||
- replace the user library with generic piano sounds
|
||||
|
||||
The right interpretation is:
|
||||
|
||||
- use `HARMONY_*_MIDI` as the musical spine
|
||||
- blend that spine with the user's library
|
||||
- keep the project library-first-hybrid
|
||||
- make the MIDI audible, useful and persistent throughout the song
|
||||
|
||||
## Current Problems To Solve
|
||||
|
||||
The current system has improved, but the open-project result still tends to show:
|
||||
|
||||
- too much geometric symmetry
|
||||
- repeated section shapes with near-identical spacing
|
||||
- too many silence islands
|
||||
- a good 4-second loop followed by dead air
|
||||
- harmonic MIDI backbone still too weak or too local
|
||||
- sections differentiated by removal instead of transformation
|
||||
- sound selection still too conservative or too repetitive
|
||||
- aggressive snares sometimes damaging coherence
|
||||
|
||||
The specific snare to watch carefully is:
|
||||
|
||||
- `SS_RNBL_Me_Gustas_One_Shot_Snare.wav`
|
||||
|
||||
Do **not** hard-ban it blindly.
|
||||
|
||||
Instead:
|
||||
|
||||
- analyze when it is appropriate
|
||||
- score it more selectively by section energy, density and surrounding sources
|
||||
- stop it from dominating softer sections or smoother grooves
|
||||
|
||||
## Scope
|
||||
|
||||
This sprint has two parallel tracks that must meet in one validated result.
|
||||
|
||||
### Track A - MCP Capability
|
||||
|
||||
Strengthen the public MCP editing workflow for an already-open project.
|
||||
|
||||
The target is not theoretical wrappers.
|
||||
|
||||
The target is:
|
||||
|
||||
- inspect open project
|
||||
- inspect clips/devices/parameters
|
||||
- edit Arrangement
|
||||
- edit MIDI in Arrangement
|
||||
- edit device parameters by name
|
||||
- use repair tools meaningfully on a real project
|
||||
|
||||
### Track B - Project Editing
|
||||
|
||||
Apply those tools to `song.als` and improve the real set.
|
||||
|
||||
Do not stop at “tools existâ€.
|
||||
|
||||
Use them.
|
||||
|
||||
## Required Outcomes
|
||||
|
||||
### A. Open-project editing must be real
|
||||
|
||||
At least these MCP abilities must be validated live on the open project:
|
||||
|
||||
- inspect tracks
|
||||
- inspect clips on a track
|
||||
- inspect a specific clip
|
||||
- inspect devices on a track
|
||||
- inspect device parameters
|
||||
- set a device parameter by name
|
||||
- create or duplicate Arrangement material
|
||||
- add MIDI notes in Arrangement
|
||||
- retrieve enough project state to support editing decisions
|
||||
|
||||
If any of these are still analysis-only or fallback-only, say so explicitly.
|
||||
|
||||
Do not mark them complete unless they were exercised against the open Live project.
|
||||
|
||||
### B. Harmonic MIDI backbone must become real arrangement content
|
||||
|
||||
The harmonic MIDI backbone must:
|
||||
|
||||
- exist in Arrangement, not just Session
|
||||
- span much more of the song, not only one local region
|
||||
- be audible and useful for continuity
|
||||
- help fill gaps where the arrangement currently drops out
|
||||
- support the user library rather than replacing it
|
||||
|
||||
It is acceptable if the timbre is pluck/keys/pad/synth instead of literal piano.
|
||||
|
||||
It is not acceptable if the MIDI exists only as metadata or one hidden clip.
|
||||
|
||||
### C. Coherence must improve without becoming sterile
|
||||
|
||||
The edit pass must reduce:
|
||||
|
||||
- mirrored section pairs
|
||||
- dead gaps between phrases
|
||||
- silence islands
|
||||
- same-source overuse
|
||||
- section-to-section copy-paste feel
|
||||
|
||||
At the same time, it must preserve:
|
||||
|
||||
- clear structure
|
||||
- strong recognizable motif
|
||||
- coherent sound family choices
|
||||
- continuity across drums, bass and harmony
|
||||
|
||||
Do not solve “repetition†by randomizing everything.
|
||||
Do not solve “coherence†by making every section identical.
|
||||
|
||||
### D. Sound freedom with discipline
|
||||
|
||||
The system should gain a bit more freedom in sound choice, but inside a coherent frame.
|
||||
|
||||
That means:
|
||||
|
||||
- allow controlled variation of support layers
|
||||
- allow section-aware alternates
|
||||
- keep identity layers constrained
|
||||
- avoid collapsing everything into 3-4 sounds
|
||||
- avoid hard lock to one exact symmetric loop
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
The sprint is complete only if all of these are true:
|
||||
|
||||
1. the open project `song.als` was used as the validation target
|
||||
2. MCP editing tools were validated live, not just compiled
|
||||
3. the open project received a real edit pass
|
||||
4. harmonic MIDI backbone exists in Arrangement and covers materially more of the song
|
||||
5. silence islands and mirrored symmetry were measured before and after
|
||||
6. the result is less empty and less repetitive without losing structure
|
||||
7. sound selection logic for aggressive snares became more selective instead of a blind blacklist
|
||||
8. all changed Python files compile
|
||||
9. relevant tests pass
|
||||
10. the report includes exact MCP calls used on the open project
|
||||
11. the report includes exact before/after coherence metrics
|
||||
12. Codex final verdict is `pass`
|
||||
|
||||
## Mandatory Validation
|
||||
|
||||
Validation must include all of the following:
|
||||
|
||||
### 1. Code validation
|
||||
|
||||
- `python -m py_compile` on every changed Python file
|
||||
- relevant tests for MCP/runtime/coherence
|
||||
|
||||
### 2. Live validation
|
||||
|
||||
Against the open Ableton project:
|
||||
|
||||
- `get_session_info()`
|
||||
- `get_tracks()`
|
||||
- `get_track_info(...)`
|
||||
- the editing tools exercised for real
|
||||
|
||||
### 3. Project audit
|
||||
|
||||
Run project-facing audits before and after the edit pass, including:
|
||||
|
||||
- silence islands
|
||||
- mirrored section pairs
|
||||
- harmonic coverage / backbone status
|
||||
- same-source dominance or reuse
|
||||
- repeated clip overuse if available
|
||||
|
||||
### 4. Audible sanity
|
||||
|
||||
The report must state clearly:
|
||||
|
||||
- whether the harmonic MIDI is actually audible
|
||||
- whether gaps were reduced
|
||||
- whether the project still feels too symmetric
|
||||
- whether the snare selectivity improved
|
||||
|
||||
## Constraints
|
||||
|
||||
- Do not generate a new song from scratch.
|
||||
- Do not replace the project with a fresh template.
|
||||
- Do not rely on Session-only material and then claim Arrangement success.
|
||||
- Do not treat wrappers or helper functions as success.
|
||||
- Do not add vocals.
|
||||
- Do not force literal piano timbre just because the user said “piano rollâ€.
|
||||
- Do not hard-ban `SS_RNBL_Me_Gustas_One_Shot_Snare.wav`; score it contextually.
|
||||
- Do not overclaim if the edit tools still depend on fragile transport hacks.
|
||||
- Do not mark complete if the live project still looks obviously symmetrical and full of dead gaps.
|
||||
|
||||
## Implementation Guidance
|
||||
|
||||
### For MCP capability work
|
||||
|
||||
Prioritize tools that matter for editing open projects:
|
||||
|
||||
- clip inspection
|
||||
- device inspection
|
||||
- parameter editing by name
|
||||
- Arrangement MIDI editing
|
||||
- duplication and continuity repair
|
||||
- project audit tools that reflect real editing pain
|
||||
|
||||
If a tool remains inherently limited by the Live API, document the exact limit and the exact fallback.
|
||||
|
||||
### For project editing work
|
||||
|
||||
Use the project audit as the source of truth.
|
||||
|
||||
Then make targeted edits:
|
||||
|
||||
- extend harmonic continuity
|
||||
- reduce dead air
|
||||
- break mirrored copy-paste shapes
|
||||
- vary support layers across sections without losing family identity
|
||||
- tighten selection of snare/clap layers by context
|
||||
|
||||
Preferred musical strategy:
|
||||
|
||||
- strong recurring identity
|
||||
- long-form harmonic support
|
||||
- fewer abrupt disappearances
|
||||
- section evolution by mutation, layering and phrasing
|
||||
- support layers changing more than core identity layers
|
||||
|
||||
## Required Deliverables
|
||||
|
||||
The implementing swarm must produce:
|
||||
|
||||
1. code changes
|
||||
2. a validation report:
|
||||
- `docs/SPRINT_v0.1.41_VALIDATION_REPORT.md`
|
||||
3. if needed, one or more exported JSON artifacts under `temp/`
|
||||
4. explicit list of changed files
|
||||
5. exact MCP calls used
|
||||
6. before/after metric table
|
||||
7. a short section titled `Remaining Risks`
|
||||
|
||||
## Report Format
|
||||
|
||||
The validation report must contain these sections:
|
||||
|
||||
1. `Summary`
|
||||
2. `Files Changed`
|
||||
3. `MCP Tools Validated Live`
|
||||
4. `Project Edits Applied`
|
||||
5. `Before/After Metrics`
|
||||
6. `Snare Selectivity`
|
||||
7. `Harmonic MIDI Backbone`
|
||||
8. `What Is Still Weak`
|
||||
9. `Remaining Risks`
|
||||
|
||||
## Failure Conditions
|
||||
|
||||
The sprint automatically fails if any of these happen:
|
||||
|
||||
- the work validates against a new generated song instead of `song.als`
|
||||
- `HARMONY_*_MIDI` is still absent from Arrangement in a meaningful way
|
||||
- the project still has large obvious silence islands with no explanation
|
||||
- the report claims success but the set still looks geometrically mirrored
|
||||
- snare handling is “fixed†by crude blacklist instead of contextual scoring
|
||||
- Codex final verdict is not `pass`
|
||||
|
||||
## Recommended Ralph Routing
|
||||
|
||||
Use the Ralph defaults already configured locally:
|
||||
|
||||
- implementer: `zai_glm51`
|
||||
- reviewers: `dashscope_qwen3coder_plus`, `dashscope_glm5`
|
||||
- Codex master: enabled
|
||||
|
||||
## Operator Note
|
||||
|
||||
This task is intended for the 24/7 Ralph queue.
|
||||
|
||||
Suggested submit command:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Submit-RalphTask.ps1 `
|
||||
-SourceFile .\docs\SPRINT_v0.1.41_NEXT_RALPH_OPEN_PROJECT_EDITING.md
|
||||
```
|
||||
@@ -0,0 +1,299 @@
|
||||
# SPRINT v0.1.41 - Ralph Swarm - Open Project Editing and Coherence
|
||||
|
||||
## Goal
|
||||
|
||||
Use the Ralph swarm to work on the already-open Ableton project:
|
||||
|
||||
- `C:\Users\ren\Desktop\song Project\song.als`
|
||||
|
||||
This is not a new-song generation sprint.
|
||||
|
||||
This sprint is about:
|
||||
|
||||
1. giving the MCP more real editing power over open projects
|
||||
2. using those tools on the current open project
|
||||
3. improving coherence, continuity and musical identity
|
||||
4. reducing symmetry, repeated blocks and empty gaps
|
||||
5. treating harmonic MIDI as a real backbone across the full arrangement
|
||||
|
||||
## Important Product Clarification
|
||||
|
||||
The user comes from FL Studio and may say `piano roll`.
|
||||
|
||||
In this project that means:
|
||||
|
||||
- harmonic MIDI backbone
|
||||
- long-form arrangement MIDI that carries harmony/melodic identity
|
||||
- editable MIDI clips in Arrangement
|
||||
|
||||
It does **not** mean:
|
||||
|
||||
- force piano timbre everywhere
|
||||
- spam piano loops
|
||||
- replace the user library with generic piano sounds
|
||||
|
||||
The right interpretation is:
|
||||
|
||||
- use `HARMONY_*_MIDI` as the musical spine
|
||||
- blend that spine with the user's library
|
||||
- keep the project library-first-hybrid
|
||||
- make the MIDI audible, useful and persistent throughout the song
|
||||
|
||||
## Current Problems To Solve
|
||||
|
||||
The current system has improved, but the open-project result still tends to show:
|
||||
|
||||
- too much geometric symmetry
|
||||
- repeated section shapes with near-identical spacing
|
||||
- too many silence islands
|
||||
- a good 4-second loop followed by dead air
|
||||
- harmonic MIDI backbone still too weak or too local
|
||||
- sections differentiated by removal instead of transformation
|
||||
- sound selection still too conservative or too repetitive
|
||||
- aggressive snares sometimes damaging coherence
|
||||
|
||||
The specific snare to watch carefully is:
|
||||
|
||||
- `SS_RNBL_Me_Gustas_One_Shot_Snare.wav`
|
||||
|
||||
Do **not** hard-ban it blindly.
|
||||
|
||||
Instead:
|
||||
|
||||
- analyze when it is appropriate
|
||||
- score it more selectively by section energy, density and surrounding sources
|
||||
- stop it from dominating softer sections or smoother grooves
|
||||
|
||||
## Scope
|
||||
|
||||
This sprint has two parallel tracks that must meet in one validated result.
|
||||
|
||||
### Track A - MCP Capability
|
||||
|
||||
Strengthen the public MCP editing workflow for an already-open project.
|
||||
|
||||
The target is not theoretical wrappers.
|
||||
|
||||
The target is:
|
||||
|
||||
- inspect open project
|
||||
- inspect clips/devices/parameters
|
||||
- edit Arrangement
|
||||
- edit MIDI in Arrangement
|
||||
- edit device parameters by name
|
||||
- use repair tools meaningfully on a real project
|
||||
|
||||
### Track B - Project Editing
|
||||
|
||||
Apply those tools to `song.als` and improve the real set.
|
||||
|
||||
Do not stop at “tools existâ€.
|
||||
|
||||
Use them.
|
||||
|
||||
## Required Outcomes
|
||||
|
||||
### A. Open-project editing must be real
|
||||
|
||||
At least these MCP abilities must be validated live on the open project:
|
||||
|
||||
- inspect tracks
|
||||
- inspect clips on a track
|
||||
- inspect a specific clip
|
||||
- inspect devices on a track
|
||||
- inspect device parameters
|
||||
- set a device parameter by name
|
||||
- create or duplicate Arrangement material
|
||||
- add MIDI notes in Arrangement
|
||||
- retrieve enough project state to support editing decisions
|
||||
|
||||
If any of these are still analysis-only or fallback-only, say so explicitly.
|
||||
|
||||
Do not mark them complete unless they were exercised against the open Live project.
|
||||
|
||||
### B. Harmonic MIDI backbone must become real arrangement content
|
||||
|
||||
The harmonic MIDI backbone must:
|
||||
|
||||
- exist in Arrangement, not just Session
|
||||
- span much more of the song, not only one local region
|
||||
- be audible and useful for continuity
|
||||
- help fill gaps where the arrangement currently drops out
|
||||
- support the user library rather than replacing it
|
||||
|
||||
It is acceptable if the timbre is pluck/keys/pad/synth instead of literal piano.
|
||||
|
||||
It is not acceptable if the MIDI exists only as metadata or one hidden clip.
|
||||
|
||||
### C. Coherence must improve without becoming sterile
|
||||
|
||||
The edit pass must reduce:
|
||||
|
||||
- mirrored section pairs
|
||||
- dead gaps between phrases
|
||||
- silence islands
|
||||
- same-source overuse
|
||||
- section-to-section copy-paste feel
|
||||
|
||||
At the same time, it must preserve:
|
||||
|
||||
- clear structure
|
||||
- strong recognizable motif
|
||||
- coherent sound family choices
|
||||
- continuity across drums, bass and harmony
|
||||
|
||||
Do not solve “repetition†by randomizing everything.
|
||||
Do not solve “coherence†by making every section identical.
|
||||
|
||||
### D. Sound freedom with discipline
|
||||
|
||||
The system should gain a bit more freedom in sound choice, but inside a coherent frame.
|
||||
|
||||
That means:
|
||||
|
||||
- allow controlled variation of support layers
|
||||
- allow section-aware alternates
|
||||
- keep identity layers constrained
|
||||
- avoid collapsing everything into 3-4 sounds
|
||||
- avoid hard lock to one exact symmetric loop
|
||||
|
||||
## Mandatory Validation
|
||||
|
||||
Validation must include all of the following:
|
||||
|
||||
### 1. Code validation
|
||||
|
||||
- `python -m py_compile` on every changed Python file
|
||||
- relevant tests for MCP/runtime/coherence
|
||||
|
||||
### 2. Live validation
|
||||
|
||||
Against the open Ableton project:
|
||||
|
||||
- `get_session_info()`
|
||||
- `get_tracks()`
|
||||
- `get_track_info(...)`
|
||||
- the editing tools exercised for real
|
||||
|
||||
### 3. Project audit
|
||||
|
||||
Run project-facing audits before and after the edit pass, including:
|
||||
|
||||
- silence islands
|
||||
- mirrored section pairs
|
||||
- harmonic coverage / backbone status
|
||||
- same-source dominance or reuse
|
||||
- repeated clip overuse if available
|
||||
|
||||
### 4. Audible sanity
|
||||
|
||||
The report must state clearly:
|
||||
|
||||
- whether the harmonic MIDI is actually audible
|
||||
- whether gaps were reduced
|
||||
- whether the project still feels too symmetric
|
||||
- whether the snare selectivity improved
|
||||
|
||||
## Constraints
|
||||
|
||||
- Do not generate a new song from scratch.
|
||||
- Do not replace the project with a fresh template.
|
||||
- Do not rely on Session-only material and then claim Arrangement success.
|
||||
- Do not treat wrappers or helper functions as success.
|
||||
- Do not add vocals.
|
||||
- Do not force literal piano timbre just because the user said “piano rollâ€.
|
||||
- Do not hard-ban `SS_RNBL_Me_Gustas_One_Shot_Snare.wav`; score it contextually.
|
||||
- Do not overclaim if the edit tools still depend on fragile transport hacks.
|
||||
- Do not mark complete if the live project still looks obviously symmetrical and full of dead gaps.
|
||||
|
||||
## Implementation Guidance
|
||||
|
||||
### For MCP capability work
|
||||
|
||||
Prioritize tools that matter for editing open projects:
|
||||
|
||||
- clip inspection
|
||||
- device inspection
|
||||
- parameter editing by name
|
||||
- Arrangement MIDI editing
|
||||
- duplication and continuity repair
|
||||
- project audit tools that reflect real editing pain
|
||||
|
||||
If a tool remains inherently limited by the Live API, document the exact limit and the exact fallback.
|
||||
|
||||
### For project editing work
|
||||
|
||||
Use the project audit as the source of truth.
|
||||
|
||||
Then make targeted edits:
|
||||
|
||||
- extend harmonic continuity
|
||||
- reduce dead air
|
||||
- break mirrored copy-paste shapes
|
||||
- vary support layers across sections without losing family identity
|
||||
- tighten selection of snare/clap layers by context
|
||||
|
||||
Preferred musical strategy:
|
||||
|
||||
- strong recurring identity
|
||||
- long-form harmonic support
|
||||
- fewer abrupt disappearances
|
||||
- section evolution by mutation, layering and phrasing
|
||||
- support layers changing more than core identity layers
|
||||
|
||||
## Required Deliverables
|
||||
|
||||
The implementing swarm must produce:
|
||||
|
||||
1. code changes
|
||||
2. a validation report:
|
||||
- `docs/SPRINT_v0.1.41_VALIDATION_REPORT.md`
|
||||
3. if needed, one or more exported JSON artifacts under `temp/`
|
||||
4. explicit list of changed files
|
||||
5. exact MCP calls used
|
||||
6. before/after metric table
|
||||
7. a short section titled `Remaining Risks`
|
||||
|
||||
## Report Format
|
||||
|
||||
The validation report must contain these sections:
|
||||
|
||||
1. `Summary`
|
||||
2. `Files Changed`
|
||||
3. `MCP Tools Validated Live`
|
||||
4. `Project Edits Applied`
|
||||
5. `Before/After Metrics`
|
||||
6. `Snare Selectivity`
|
||||
7. `Harmonic MIDI Backbone`
|
||||
8. `What Is Still Weak`
|
||||
9. `Remaining Risks`
|
||||
|
||||
## Failure Conditions
|
||||
|
||||
The sprint automatically fails if any of these happen:
|
||||
|
||||
- the work validates against a new generated song instead of `song.als`
|
||||
- `HARMONY_*_MIDI` is still absent from Arrangement in a meaningful way
|
||||
- the project still has large obvious silence islands with no explanation
|
||||
- the report claims success but the set still looks geometrically mirrored
|
||||
- snare handling is “fixed†by crude blacklist instead of contextual scoring
|
||||
- Codex final verdict is not `pass`
|
||||
|
||||
## Recommended Ralph Routing
|
||||
|
||||
Use the Ralph defaults already configured locally:
|
||||
|
||||
- implementer: `zai_glm51`
|
||||
- reviewers: `dashscope_qwen3coder_plus`, `dashscope_glm5`
|
||||
- Codex master: enabled
|
||||
|
||||
## Operator Note
|
||||
|
||||
This task is intended for the 24/7 Ralph queue.
|
||||
|
||||
Suggested submit command:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Submit-RalphTask.ps1 `
|
||||
-SourceFile .\docs\SPRINT_v0.1.41_NEXT_RALPH_OPEN_PROJECT_EDITING.md
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"id": "20260403-193730-20260403-193721-sprint-v0-1-41-ralph-swarm-open",
|
||||
"title": "20260403-193721-sprint-v0-1-41-ralph-swarm-open-project-editing",
|
||||
"state": "failed",
|
||||
"finished_at": "2026-04-03T19:42:06.4487683-03:00",
|
||||
"source": "C:\\ProgramData\\Ableton\\Live 12 Suite\\Resources\\MIDI Remote Scripts\\ralph\\tasks\\inbox\\20260403-193721-sprint-v0-1-41-ralph-swarm-open-project-editing",
|
||||
"run_id": "",
|
||||
"summary": "Start-RalphAutopilot.ps1 failed with exit code 1"
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
The sprint is complete only if all of these are true:
|
||||
|
||||
1. the open project `song.als` was used as the validation target
|
||||
2. MCP editing tools were validated live, not just compiled
|
||||
3. the open project received a real edit pass
|
||||
4. harmonic MIDI backbone exists in Arrangement and covers materially more of the song
|
||||
5. silence islands and mirrored symmetry were measured before and after
|
||||
6. the result is less empty and less repetitive without losing structure
|
||||
7. sound selection logic for aggressive snares became more selective instead of a blind blacklist
|
||||
8. all changed Python files compile
|
||||
9. relevant tests pass
|
||||
10. the report includes exact MCP calls used on the open project
|
||||
11. the report includes exact before/after coherence metrics
|
||||
12. Codex final verdict is `pass`
|
||||
@@ -0,0 +1,9 @@
|
||||
# Context
|
||||
|
||||
List the canonical docs and code paths the worker must read first.
|
||||
|
||||
- active sprint doc
|
||||
- current handoff
|
||||
- relevant source files
|
||||
- known evidence files
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
# SPRINT v0.1.41 - Ralph Swarm - Open Project Editing and Coherence
|
||||
|
||||
## Goal
|
||||
|
||||
Use the Ralph swarm to work on the already-open Ableton project:
|
||||
|
||||
- `C:\Users\ren\Desktop\song Project\song.als`
|
||||
|
||||
This is not a new-song generation sprint.
|
||||
|
||||
This sprint is about:
|
||||
|
||||
1. giving the MCP more real editing power over open projects
|
||||
2. using those tools on the current open project
|
||||
3. improving coherence, continuity and musical identity
|
||||
4. reducing symmetry, repeated blocks and empty gaps
|
||||
5. treating harmonic MIDI as a real backbone across the full arrangement
|
||||
|
||||
## Important Product Clarification
|
||||
|
||||
The user comes from FL Studio and may say `piano roll`.
|
||||
|
||||
In this project that means:
|
||||
|
||||
- harmonic MIDI backbone
|
||||
- long-form arrangement MIDI that carries harmony/melodic identity
|
||||
- editable MIDI clips in Arrangement
|
||||
|
||||
It does **not** mean:
|
||||
|
||||
- force piano timbre everywhere
|
||||
- spam piano loops
|
||||
- replace the user library with generic piano sounds
|
||||
|
||||
The right interpretation is:
|
||||
|
||||
- use `HARMONY_*_MIDI` as the musical spine
|
||||
- blend that spine with the user's library
|
||||
- keep the project library-first-hybrid
|
||||
- make the MIDI audible, useful and persistent throughout the song
|
||||
|
||||
## Current Problems To Solve
|
||||
|
||||
The current system has improved, but the open-project result still tends to show:
|
||||
|
||||
- too much geometric symmetry
|
||||
- repeated section shapes with near-identical spacing
|
||||
- too many silence islands
|
||||
- a good 4-second loop followed by dead air
|
||||
- harmonic MIDI backbone still too weak or too local
|
||||
- sections differentiated by removal instead of transformation
|
||||
- sound selection still too conservative or too repetitive
|
||||
- aggressive snares sometimes damaging coherence
|
||||
|
||||
The specific snare to watch carefully is:
|
||||
|
||||
- `SS_RNBL_Me_Gustas_One_Shot_Snare.wav`
|
||||
|
||||
Do **not** hard-ban it blindly.
|
||||
|
||||
Instead:
|
||||
|
||||
- analyze when it is appropriate
|
||||
- score it more selectively by section energy, density and surrounding sources
|
||||
- stop it from dominating softer sections or smoother grooves
|
||||
|
||||
## Scope
|
||||
|
||||
This sprint has two parallel tracks that must meet in one validated result.
|
||||
|
||||
### Track A - MCP Capability
|
||||
|
||||
Strengthen the public MCP editing workflow for an already-open project.
|
||||
|
||||
The target is not theoretical wrappers.
|
||||
|
||||
The target is:
|
||||
|
||||
- inspect open project
|
||||
- inspect clips/devices/parameters
|
||||
- edit Arrangement
|
||||
- edit MIDI in Arrangement
|
||||
- edit device parameters by name
|
||||
- use repair tools meaningfully on a real project
|
||||
|
||||
### Track B - Project Editing
|
||||
|
||||
Apply those tools to `song.als` and improve the real set.
|
||||
|
||||
Do not stop at “tools existâ€.
|
||||
|
||||
Use them.
|
||||
|
||||
## Required Outcomes
|
||||
|
||||
### A. Open-project editing must be real
|
||||
|
||||
At least these MCP abilities must be validated live on the open project:
|
||||
|
||||
- inspect tracks
|
||||
- inspect clips on a track
|
||||
- inspect a specific clip
|
||||
- inspect devices on a track
|
||||
- inspect device parameters
|
||||
- set a device parameter by name
|
||||
- create or duplicate Arrangement material
|
||||
- add MIDI notes in Arrangement
|
||||
- retrieve enough project state to support editing decisions
|
||||
|
||||
If any of these are still analysis-only or fallback-only, say so explicitly.
|
||||
|
||||
Do not mark them complete unless they were exercised against the open Live project.
|
||||
|
||||
### B. Harmonic MIDI backbone must become real arrangement content
|
||||
|
||||
The harmonic MIDI backbone must:
|
||||
|
||||
- exist in Arrangement, not just Session
|
||||
- span much more of the song, not only one local region
|
||||
- be audible and useful for continuity
|
||||
- help fill gaps where the arrangement currently drops out
|
||||
- support the user library rather than replacing it
|
||||
|
||||
It is acceptable if the timbre is pluck/keys/pad/synth instead of literal piano.
|
||||
|
||||
It is not acceptable if the MIDI exists only as metadata or one hidden clip.
|
||||
|
||||
### C. Coherence must improve without becoming sterile
|
||||
|
||||
The edit pass must reduce:
|
||||
|
||||
- mirrored section pairs
|
||||
- dead gaps between phrases
|
||||
- silence islands
|
||||
- same-source overuse
|
||||
- section-to-section copy-paste feel
|
||||
|
||||
At the same time, it must preserve:
|
||||
|
||||
- clear structure
|
||||
- strong recognizable motif
|
||||
- coherent sound family choices
|
||||
- continuity across drums, bass and harmony
|
||||
|
||||
Do not solve “repetition†by randomizing everything.
|
||||
Do not solve “coherence†by making every section identical.
|
||||
|
||||
### D. Sound freedom with discipline
|
||||
|
||||
The system should gain a bit more freedom in sound choice, but inside a coherent frame.
|
||||
|
||||
That means:
|
||||
|
||||
- allow controlled variation of support layers
|
||||
- allow section-aware alternates
|
||||
- keep identity layers constrained
|
||||
- avoid collapsing everything into 3-4 sounds
|
||||
- avoid hard lock to one exact symmetric loop
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
The sprint is complete only if all of these are true:
|
||||
|
||||
1. the open project `song.als` was used as the validation target
|
||||
2. MCP editing tools were validated live, not just compiled
|
||||
3. the open project received a real edit pass
|
||||
4. harmonic MIDI backbone exists in Arrangement and covers materially more of the song
|
||||
5. silence islands and mirrored symmetry were measured before and after
|
||||
6. the result is less empty and less repetitive without losing structure
|
||||
7. sound selection logic for aggressive snares became more selective instead of a blind blacklist
|
||||
8. all changed Python files compile
|
||||
9. relevant tests pass
|
||||
10. the report includes exact MCP calls used on the open project
|
||||
11. the report includes exact before/after coherence metrics
|
||||
12. Codex final verdict is `pass`
|
||||
|
||||
## Mandatory Validation
|
||||
|
||||
Validation must include all of the following:
|
||||
|
||||
### 1. Code validation
|
||||
|
||||
- `python -m py_compile` on every changed Python file
|
||||
- relevant tests for MCP/runtime/coherence
|
||||
|
||||
### 2. Live validation
|
||||
|
||||
Against the open Ableton project:
|
||||
|
||||
- `get_session_info()`
|
||||
- `get_tracks()`
|
||||
- `get_track_info(...)`
|
||||
- the editing tools exercised for real
|
||||
|
||||
### 3. Project audit
|
||||
|
||||
Run project-facing audits before and after the edit pass, including:
|
||||
|
||||
- silence islands
|
||||
- mirrored section pairs
|
||||
- harmonic coverage / backbone status
|
||||
- same-source dominance or reuse
|
||||
- repeated clip overuse if available
|
||||
|
||||
### 4. Audible sanity
|
||||
|
||||
The report must state clearly:
|
||||
|
||||
- whether the harmonic MIDI is actually audible
|
||||
- whether gaps were reduced
|
||||
- whether the project still feels too symmetric
|
||||
- whether the snare selectivity improved
|
||||
|
||||
## Constraints
|
||||
|
||||
- Do not generate a new song from scratch.
|
||||
- Do not replace the project with a fresh template.
|
||||
- Do not rely on Session-only material and then claim Arrangement success.
|
||||
- Do not treat wrappers or helper functions as success.
|
||||
- Do not add vocals.
|
||||
- Do not force literal piano timbre just because the user said “piano rollâ€.
|
||||
- Do not hard-ban `SS_RNBL_Me_Gustas_One_Shot_Snare.wav`; score it contextually.
|
||||
- Do not overclaim if the edit tools still depend on fragile transport hacks.
|
||||
- Do not mark complete if the live project still looks obviously symmetrical and full of dead gaps.
|
||||
|
||||
## Implementation Guidance
|
||||
|
||||
### For MCP capability work
|
||||
|
||||
Prioritize tools that matter for editing open projects:
|
||||
|
||||
- clip inspection
|
||||
- device inspection
|
||||
- parameter editing by name
|
||||
- Arrangement MIDI editing
|
||||
- duplication and continuity repair
|
||||
- project audit tools that reflect real editing pain
|
||||
|
||||
If a tool remains inherently limited by the Live API, document the exact limit and the exact fallback.
|
||||
|
||||
### For project editing work
|
||||
|
||||
Use the project audit as the source of truth.
|
||||
|
||||
Then make targeted edits:
|
||||
|
||||
- extend harmonic continuity
|
||||
- reduce dead air
|
||||
- break mirrored copy-paste shapes
|
||||
- vary support layers across sections without losing family identity
|
||||
- tighten selection of snare/clap layers by context
|
||||
|
||||
Preferred musical strategy:
|
||||
|
||||
- strong recurring identity
|
||||
- long-form harmonic support
|
||||
- fewer abrupt disappearances
|
||||
- section evolution by mutation, layering and phrasing
|
||||
- support layers changing more than core identity layers
|
||||
|
||||
## Required Deliverables
|
||||
|
||||
The implementing swarm must produce:
|
||||
|
||||
1. code changes
|
||||
2. a validation report:
|
||||
- `docs/SPRINT_v0.1.41_VALIDATION_REPORT.md`
|
||||
3. if needed, one or more exported JSON artifacts under `temp/`
|
||||
4. explicit list of changed files
|
||||
5. exact MCP calls used
|
||||
6. before/after metric table
|
||||
7. a short section titled `Remaining Risks`
|
||||
|
||||
## Report Format
|
||||
|
||||
The validation report must contain these sections:
|
||||
|
||||
1. `Summary`
|
||||
2. `Files Changed`
|
||||
3. `MCP Tools Validated Live`
|
||||
4. `Project Edits Applied`
|
||||
5. `Before/After Metrics`
|
||||
6. `Snare Selectivity`
|
||||
7. `Harmonic MIDI Backbone`
|
||||
8. `What Is Still Weak`
|
||||
9. `Remaining Risks`
|
||||
|
||||
## Failure Conditions
|
||||
|
||||
The sprint automatically fails if any of these happen:
|
||||
|
||||
- the work validates against a new generated song instead of `song.als`
|
||||
- `HARMONY_*_MIDI` is still absent from Arrangement in a meaningful way
|
||||
- the project still has large obvious silence islands with no explanation
|
||||
- the report claims success but the set still looks geometrically mirrored
|
||||
- snare handling is “fixed†by crude blacklist instead of contextual scoring
|
||||
- Codex final verdict is not `pass`
|
||||
|
||||
## Recommended Ralph Routing
|
||||
|
||||
Use the Ralph defaults already configured locally:
|
||||
|
||||
- implementer: `zai_glm51`
|
||||
- reviewers: `dashscope_qwen3coder_plus`, `dashscope_glm5`
|
||||
- Codex master: enabled
|
||||
|
||||
## Operator Note
|
||||
|
||||
This task is intended for the 24/7 Ralph queue.
|
||||
|
||||
Suggested submit command:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Submit-RalphTask.ps1 `
|
||||
-SourceFile .\docs\SPRINT_v0.1.41_NEXT_RALPH_OPEN_PROJECT_EDITING.md
|
||||
```
|
||||
@@ -0,0 +1,299 @@
|
||||
# SPRINT v0.1.41 - Ralph Swarm - Open Project Editing and Coherence
|
||||
|
||||
## Goal
|
||||
|
||||
Use the Ralph swarm to work on the already-open Ableton project:
|
||||
|
||||
- `C:\Users\ren\Desktop\song Project\song.als`
|
||||
|
||||
This is not a new-song generation sprint.
|
||||
|
||||
This sprint is about:
|
||||
|
||||
1. giving the MCP more real editing power over open projects
|
||||
2. using those tools on the current open project
|
||||
3. improving coherence, continuity and musical identity
|
||||
4. reducing symmetry, repeated blocks and empty gaps
|
||||
5. treating harmonic MIDI as a real backbone across the full arrangement
|
||||
|
||||
## Important Product Clarification
|
||||
|
||||
The user comes from FL Studio and may say `piano roll`.
|
||||
|
||||
In this project that means:
|
||||
|
||||
- harmonic MIDI backbone
|
||||
- long-form arrangement MIDI that carries harmony/melodic identity
|
||||
- editable MIDI clips in Arrangement
|
||||
|
||||
It does **not** mean:
|
||||
|
||||
- force piano timbre everywhere
|
||||
- spam piano loops
|
||||
- replace the user library with generic piano sounds
|
||||
|
||||
The right interpretation is:
|
||||
|
||||
- use `HARMONY_*_MIDI` as the musical spine
|
||||
- blend that spine with the user's library
|
||||
- keep the project library-first-hybrid
|
||||
- make the MIDI audible, useful and persistent throughout the song
|
||||
|
||||
## Current Problems To Solve
|
||||
|
||||
The current system has improved, but the open-project result still tends to show:
|
||||
|
||||
- too much geometric symmetry
|
||||
- repeated section shapes with near-identical spacing
|
||||
- too many silence islands
|
||||
- a good 4-second loop followed by dead air
|
||||
- harmonic MIDI backbone still too weak or too local
|
||||
- sections differentiated by removal instead of transformation
|
||||
- sound selection still too conservative or too repetitive
|
||||
- aggressive snares sometimes damaging coherence
|
||||
|
||||
The specific snare to watch carefully is:
|
||||
|
||||
- `SS_RNBL_Me_Gustas_One_Shot_Snare.wav`
|
||||
|
||||
Do **not** hard-ban it blindly.
|
||||
|
||||
Instead:
|
||||
|
||||
- analyze when it is appropriate
|
||||
- score it more selectively by section energy, density and surrounding sources
|
||||
- stop it from dominating softer sections or smoother grooves
|
||||
|
||||
## Scope
|
||||
|
||||
This sprint has two parallel tracks that must meet in one validated result.
|
||||
|
||||
### Track A - MCP Capability
|
||||
|
||||
Strengthen the public MCP editing workflow for an already-open project.
|
||||
|
||||
The target is not theoretical wrappers.
|
||||
|
||||
The target is:
|
||||
|
||||
- inspect open project
|
||||
- inspect clips/devices/parameters
|
||||
- edit Arrangement
|
||||
- edit MIDI in Arrangement
|
||||
- edit device parameters by name
|
||||
- use repair tools meaningfully on a real project
|
||||
|
||||
### Track B - Project Editing
|
||||
|
||||
Apply those tools to `song.als` and improve the real set.
|
||||
|
||||
Do not stop at “tools existâ€.
|
||||
|
||||
Use them.
|
||||
|
||||
## Required Outcomes
|
||||
|
||||
### A. Open-project editing must be real
|
||||
|
||||
At least these MCP abilities must be validated live on the open project:
|
||||
|
||||
- inspect tracks
|
||||
- inspect clips on a track
|
||||
- inspect a specific clip
|
||||
- inspect devices on a track
|
||||
- inspect device parameters
|
||||
- set a device parameter by name
|
||||
- create or duplicate Arrangement material
|
||||
- add MIDI notes in Arrangement
|
||||
- retrieve enough project state to support editing decisions
|
||||
|
||||
If any of these are still analysis-only or fallback-only, say so explicitly.
|
||||
|
||||
Do not mark them complete unless they were exercised against the open Live project.
|
||||
|
||||
### B. Harmonic MIDI backbone must become real arrangement content
|
||||
|
||||
The harmonic MIDI backbone must:
|
||||
|
||||
- exist in Arrangement, not just Session
|
||||
- span much more of the song, not only one local region
|
||||
- be audible and useful for continuity
|
||||
- help fill gaps where the arrangement currently drops out
|
||||
- support the user library rather than replacing it
|
||||
|
||||
It is acceptable if the timbre is pluck/keys/pad/synth instead of literal piano.
|
||||
|
||||
It is not acceptable if the MIDI exists only as metadata or one hidden clip.
|
||||
|
||||
### C. Coherence must improve without becoming sterile
|
||||
|
||||
The edit pass must reduce:
|
||||
|
||||
- mirrored section pairs
|
||||
- dead gaps between phrases
|
||||
- silence islands
|
||||
- same-source overuse
|
||||
- section-to-section copy-paste feel
|
||||
|
||||
At the same time, it must preserve:
|
||||
|
||||
- clear structure
|
||||
- strong recognizable motif
|
||||
- coherent sound family choices
|
||||
- continuity across drums, bass and harmony
|
||||
|
||||
Do not solve “repetition†by randomizing everything.
|
||||
Do not solve “coherence†by making every section identical.
|
||||
|
||||
### D. Sound freedom with discipline
|
||||
|
||||
The system should gain a bit more freedom in sound choice, but inside a coherent frame.
|
||||
|
||||
That means:
|
||||
|
||||
- allow controlled variation of support layers
|
||||
- allow section-aware alternates
|
||||
- keep identity layers constrained
|
||||
- avoid collapsing everything into 3-4 sounds
|
||||
- avoid hard lock to one exact symmetric loop
|
||||
|
||||
## Mandatory Validation
|
||||
|
||||
Validation must include all of the following:
|
||||
|
||||
### 1. Code validation
|
||||
|
||||
- `python -m py_compile` on every changed Python file
|
||||
- relevant tests for MCP/runtime/coherence
|
||||
|
||||
### 2. Live validation
|
||||
|
||||
Against the open Ableton project:
|
||||
|
||||
- `get_session_info()`
|
||||
- `get_tracks()`
|
||||
- `get_track_info(...)`
|
||||
- the editing tools exercised for real
|
||||
|
||||
### 3. Project audit
|
||||
|
||||
Run project-facing audits before and after the edit pass, including:
|
||||
|
||||
- silence islands
|
||||
- mirrored section pairs
|
||||
- harmonic coverage / backbone status
|
||||
- same-source dominance or reuse
|
||||
- repeated clip overuse if available
|
||||
|
||||
### 4. Audible sanity
|
||||
|
||||
The report must state clearly:
|
||||
|
||||
- whether the harmonic MIDI is actually audible
|
||||
- whether gaps were reduced
|
||||
- whether the project still feels too symmetric
|
||||
- whether the snare selectivity improved
|
||||
|
||||
## Constraints
|
||||
|
||||
- Do not generate a new song from scratch.
|
||||
- Do not replace the project with a fresh template.
|
||||
- Do not rely on Session-only material and then claim Arrangement success.
|
||||
- Do not treat wrappers or helper functions as success.
|
||||
- Do not add vocals.
|
||||
- Do not force literal piano timbre just because the user said “piano rollâ€.
|
||||
- Do not hard-ban `SS_RNBL_Me_Gustas_One_Shot_Snare.wav`; score it contextually.
|
||||
- Do not overclaim if the edit tools still depend on fragile transport hacks.
|
||||
- Do not mark complete if the live project still looks obviously symmetrical and full of dead gaps.
|
||||
|
||||
## Implementation Guidance
|
||||
|
||||
### For MCP capability work
|
||||
|
||||
Prioritize tools that matter for editing open projects:
|
||||
|
||||
- clip inspection
|
||||
- device inspection
|
||||
- parameter editing by name
|
||||
- Arrangement MIDI editing
|
||||
- duplication and continuity repair
|
||||
- project audit tools that reflect real editing pain
|
||||
|
||||
If a tool remains inherently limited by the Live API, document the exact limit and the exact fallback.
|
||||
|
||||
### For project editing work
|
||||
|
||||
Use the project audit as the source of truth.
|
||||
|
||||
Then make targeted edits:
|
||||
|
||||
- extend harmonic continuity
|
||||
- reduce dead air
|
||||
- break mirrored copy-paste shapes
|
||||
- vary support layers across sections without losing family identity
|
||||
- tighten selection of snare/clap layers by context
|
||||
|
||||
Preferred musical strategy:
|
||||
|
||||
- strong recurring identity
|
||||
- long-form harmonic support
|
||||
- fewer abrupt disappearances
|
||||
- section evolution by mutation, layering and phrasing
|
||||
- support layers changing more than core identity layers
|
||||
|
||||
## Required Deliverables
|
||||
|
||||
The implementing swarm must produce:
|
||||
|
||||
1. code changes
|
||||
2. a validation report:
|
||||
- `docs/SPRINT_v0.1.41_VALIDATION_REPORT.md`
|
||||
3. if needed, one or more exported JSON artifacts under `temp/`
|
||||
4. explicit list of changed files
|
||||
5. exact MCP calls used
|
||||
6. before/after metric table
|
||||
7. a short section titled `Remaining Risks`
|
||||
|
||||
## Report Format
|
||||
|
||||
The validation report must contain these sections:
|
||||
|
||||
1. `Summary`
|
||||
2. `Files Changed`
|
||||
3. `MCP Tools Validated Live`
|
||||
4. `Project Edits Applied`
|
||||
5. `Before/After Metrics`
|
||||
6. `Snare Selectivity`
|
||||
7. `Harmonic MIDI Backbone`
|
||||
8. `What Is Still Weak`
|
||||
9. `Remaining Risks`
|
||||
|
||||
## Failure Conditions
|
||||
|
||||
The sprint automatically fails if any of these happen:
|
||||
|
||||
- the work validates against a new generated song instead of `song.als`
|
||||
- `HARMONY_*_MIDI` is still absent from Arrangement in a meaningful way
|
||||
- the project still has large obvious silence islands with no explanation
|
||||
- the report claims success but the set still looks geometrically mirrored
|
||||
- snare handling is “fixed†by crude blacklist instead of contextual scoring
|
||||
- Codex final verdict is not `pass`
|
||||
|
||||
## Recommended Ralph Routing
|
||||
|
||||
Use the Ralph defaults already configured locally:
|
||||
|
||||
- implementer: `zai_glm51`
|
||||
- reviewers: `dashscope_qwen3coder_plus`, `dashscope_glm5`
|
||||
- Codex master: enabled
|
||||
|
||||
## Operator Note
|
||||
|
||||
This task is intended for the 24/7 Ralph queue.
|
||||
|
||||
Suggested submit command:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Submit-RalphTask.ps1 `
|
||||
-SourceFile .\docs\SPRINT_v0.1.41_NEXT_RALPH_OPEN_PROJECT_EDITING.md
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"id": "20260403-195932-20260403-195932-sprint-v0-1-41-ralph-swarm-open",
|
||||
"title": "20260403-195932-sprint-v0-1-41-ralph-swarm-open-project-editing",
|
||||
"state": "failed",
|
||||
"finished_at": "2026-04-03T20:22:44.1704526-03:00",
|
||||
"source": "C:\\ProgramData\\Ableton\\Live 12 Suite\\Resources\\MIDI Remote Scripts\\ralph\\tasks\\inbox\\20260403-195932-sprint-v0-1-41-ralph-swarm-open-project-editing",
|
||||
"run_id": "",
|
||||
"summary": "Start-RalphAutopilot.ps1 failed with exit code 1"
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
This sprint passes only if all of the following are true:
|
||||
|
||||
1. The live validation target is the already-open song.als project.
|
||||
2. Exact MCP calls and exact raw results are recorded for the validated live tool sequence.
|
||||
3. At least one real Arrangement edit is applied through the MCP path on the open project.
|
||||
4. At least one real Arrangement MIDI operation is applied on the open project, or the report proves the exact runtime limitation with raw evidence.
|
||||
5. Harmonic MIDI backbone exists in Arrangement as meaningful content over materially more of the song than before.
|
||||
6. Before and after project-audit metrics are captured from the real open project with exact values.
|
||||
7. The after state shows at least one concrete improvement in coherence metrics, such as fewer silence islands, fewer mirrored pairs, stronger harmonic coverage, or reduced repeated-source dominance.
|
||||
8. Snare selectivity is validated in the real selection path, not only described from source code.
|
||||
9. All changed Python files compile.
|
||||
10. Relevant tests for touched MCP/runtime/coherence code pass.
|
||||
11. The validation report is strict and honest about what was and was not validated live.
|
||||
12. Codex can reasonably return pass from repository evidence alone.
|
||||
|
||||
Automatic fail conditions:
|
||||
- validation remains code-only
|
||||
- no exact MCP call log exists
|
||||
- no before/after metrics exist
|
||||
- no live edit was applied to song.als
|
||||
- the run validates against a new generated song or different session
|
||||
- the report claims completion without runtime evidence
|
||||
@@ -0,0 +1,28 @@
|
||||
Read these first:
|
||||
|
||||
- previous failed run directory:
|
||||
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-195932-queue
|
||||
- previous summary:
|
||||
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-195932-queue\SUMMARY.md
|
||||
- previous reviews:
|
||||
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-195932-queue\reviews\codex_master_pre_fix.json
|
||||
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-195932-queue\reviews\opencode_qwen3coder_plus.json
|
||||
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-195932-queue\reviews\opencode_glm47.json
|
||||
- previous validation report:
|
||||
- docs/SPRINT_v0.1.41_VALIDATION_REPORT.md
|
||||
|
||||
Primary source files:
|
||||
- AbletonMCP_AI/MCP_Server/server.py
|
||||
- AbletonMCP_AI/MCP_Server/sample_selector.py
|
||||
- abletonmcp_init.py
|
||||
- AbletonMCP_AI/abletonmcp_runtime.py
|
||||
|
||||
Execution priority for this sprint:
|
||||
1. prove live target and MCP connectivity
|
||||
2. validate existing tools live
|
||||
3. minimally fix only the runtime blockers that prevent validation
|
||||
4. apply one bounded real edit pass
|
||||
5. capture before/after evidence
|
||||
6. report strictly from repository truth
|
||||
|
||||
Do not overclaim. This sprint exists to turn unvalidated open-project editing infrastructure into demonstrated live behavior on the real Ableton session.
|
||||
@@ -0,0 +1,219 @@
|
||||
# SPRINT v0.1.42 - Live Proof of Open-Project Editing
|
||||
|
||||
## Goal
|
||||
|
||||
Validate and use the existing MCP editing path on the already-open Ableton project:
|
||||
|
||||
- C:\Users\ren\Desktop\song Project\song.als
|
||||
|
||||
This sprint is not a generation sprint and not a wrapper-expansion sprint.
|
||||
|
||||
It is a runtime-proof sprint:
|
||||
|
||||
- prove the MCP can inspect the real open project
|
||||
- prove the MCP can apply real Arrangement edits on that project
|
||||
- prove the edits improve coherence in measurable ways
|
||||
- document exact evidence or fail explicitly
|
||||
|
||||
## Why This Task Exists
|
||||
|
||||
The previous run failed.
|
||||
|
||||
Repository truth from the failed run:
|
||||
|
||||
- infrastructure was added in AbletonMCP_AI/MCP_Server/server.py
|
||||
- contextual snare scoring was added in AbletonMCP_AI/MCP_Server/sample_selector.py
|
||||
- docs/SPRINT_v0.1.41_VALIDATION_REPORT.md was corrected to an honest fail
|
||||
- no live MCP calls were exercised against song.als
|
||||
- no real edit pass was applied
|
||||
- no before/after coherence metrics were captured
|
||||
- no proof exists that the new editing tools work through the real Ableton runtime path
|
||||
|
||||
The highest-signal blocker is now obvious:
|
||||
|
||||
- stop adding unvalidated infrastructure
|
||||
- prove runtime behavior on the open project
|
||||
- if runtime behavior is blocked, isolate the exact blocker with raw evidence
|
||||
|
||||
## Required Work
|
||||
|
||||
1. Prove the live target is the real open project.
|
||||
- Connect through the MCP to the active Ableton session.
|
||||
- Capture enough session evidence to prove the target is the already-open song.als session and not a generated set or blank template.
|
||||
- Save raw outputs under temp/.
|
||||
|
||||
2. Validate the existing MCP tool path live before adding more tools.
|
||||
- Exercise these tools against the open project and record exact calls plus raw outputs:
|
||||
- get_session_info()
|
||||
- get_tracks()
|
||||
- get_track_info(...)
|
||||
- get_clips(...)
|
||||
- get_clip_info(...)
|
||||
- get_devices(...)
|
||||
- get_device_parameters(...)
|
||||
- set_device_parameter_by_name(...)
|
||||
- one arrangement creation or duplication path
|
||||
- one arrangement MIDI note insertion path
|
||||
- For each tool, classify it as:
|
||||
- live validated
|
||||
- failed live
|
||||
- blocked by backend/runtime limitation
|
||||
- Do not mark a tool complete just because it compiles.
|
||||
|
||||
3. If a live tool path fails, fix only the minimal blocker.
|
||||
- Use the existing code added in v0.1.41 as the starting point.
|
||||
- If a backend handler or runtime path is missing, add the smallest fix needed in the real runtime path.
|
||||
- Re-run the exact same MCP call after the fix and save the before/after evidence.
|
||||
- Do not add unrelated new feature surface.
|
||||
|
||||
4. Audit the open project before editing.
|
||||
- Run project-facing audits on the real song.als session.
|
||||
- Capture exact before metrics for:
|
||||
- silence islands
|
||||
- mirrored section pairs
|
||||
- harmonic coverage / harmonic backbone status
|
||||
- same-source dominance or repeated-source overuse
|
||||
- repeated clip overuse if available
|
||||
- Save raw outputs under temp/.
|
||||
- Use these audits to choose the edit targets.
|
||||
|
||||
5. Apply a bounded real edit pass on song.als.
|
||||
- Use the validated MCP editing tools on the real open project.
|
||||
- The edit pass must be small, targeted, and measurable.
|
||||
- Prioritize:
|
||||
- extending or repairing harmonic MIDI backbone in Arrangement
|
||||
- reducing dead gaps
|
||||
- breaking at least one mirrored or obviously repeated arrangement shape
|
||||
- improving continuity without destroying identity
|
||||
- The harmonic MIDI backbone must become real Arrangement content intended to be audible.
|
||||
- It is acceptable to use keys, pluck, pad, or synth timbre.
|
||||
- It is not acceptable to leave the backbone as metadata, a hidden clip, or an empty placeholder.
|
||||
|
||||
6. Validate snare selectivity in the real path.
|
||||
- Do not hard-ban SS_RNBL_Me_Gustas_One_Shot_Snare.wav.
|
||||
- Prove whether the new contextual snare scoring actually affects selection in real use.
|
||||
- If the logic exists but is not wired into the live path, wire the minimal real path and validate it.
|
||||
- Record exact evidence showing whether lower-energy sections are treated more conservatively than higher-energy sections.
|
||||
|
||||
7. Re-audit after editing.
|
||||
- Run the same project-facing audits again.
|
||||
- Save raw after-state outputs under temp/.
|
||||
- Produce an exact before/after table using measured values, not estimates.
|
||||
- State clearly what improved, what stayed flat, and what regressed.
|
||||
|
||||
8. Keep the scope disciplined.
|
||||
- Touch only the files directly required to validate or minimally fix the runtime editing path.
|
||||
- Likely files:
|
||||
- AbletonMCP_AI/MCP_Server/server.py
|
||||
- AbletonMCP_AI/MCP_Server/sample_selector.py
|
||||
- abletonmcp_init.py
|
||||
- AbletonMCP_AI/abletonmcp_runtime.py
|
||||
- docs/SPRINT_v0.1.42_VALIDATION_REPORT.md
|
||||
- Do not add broad new capability areas unless they are the direct blocker to a required live validation step.
|
||||
|
||||
9. Fail fast if the runtime is not reachable.
|
||||
- If Ableton, the control surface, or the MCP connection is unavailable, do not continue building infrastructure.
|
||||
- Capture the exact failing step and raw evidence.
|
||||
- Mark the sprint failed with specific blocker evidence.
|
||||
- A code-only partial success is not acceptable for this sprint.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
This sprint passes only if all of the following are true:
|
||||
|
||||
1. The live validation target is the already-open song.als project.
|
||||
2. Exact MCP calls and exact raw results are recorded for the validated live tool sequence.
|
||||
3. At least one real Arrangement edit is applied through the MCP path on the open project.
|
||||
4. At least one real Arrangement MIDI operation is applied on the open project, or the report proves the exact runtime limitation with raw evidence.
|
||||
5. Harmonic MIDI backbone exists in Arrangement as meaningful content over materially more of the song than before.
|
||||
6. Before and after project-audit metrics are captured from the real open project with exact values.
|
||||
7. The after state shows at least one concrete improvement in coherence metrics, such as fewer silence islands, fewer mirrored pairs, stronger harmonic coverage, or reduced repeated-source dominance.
|
||||
8. Snare selectivity is validated in the real selection path, not only described from source code.
|
||||
9. All changed Python files compile.
|
||||
10. Relevant tests for touched MCP/runtime/coherence code pass.
|
||||
11. The validation report is strict and honest about what was and was not validated live.
|
||||
12. Codex can reasonably return pass from repository evidence alone.
|
||||
|
||||
Automatic fail conditions:
|
||||
- validation remains code-only
|
||||
- no exact MCP call log exists
|
||||
- no before/after metrics exist
|
||||
- no live edit was applied to song.als
|
||||
- the run validates against a new generated song or different session
|
||||
- the report claims completion without runtime evidence
|
||||
|
||||
## Validation
|
||||
|
||||
Produce all of the following artifacts:
|
||||
|
||||
1. docs/SPRINT_v0.1.42_VALIDATION_REPORT.md
|
||||
Required sections:
|
||||
- Summary
|
||||
- Files Changed
|
||||
- Live Target Proof
|
||||
- MCP Tools Validated Live
|
||||
- Project Audits Before
|
||||
- Project Edits Applied
|
||||
- Project Audits After
|
||||
- Before/After Metrics
|
||||
- Snare Selectivity
|
||||
- Harmonic MIDI Backbone
|
||||
- What Is Still Weak
|
||||
- Remaining Risks
|
||||
|
||||
2. temp/v04142_live_target_proof.json
|
||||
- raw evidence proving the active Ableton session is the intended open project
|
||||
|
||||
3. temp/v04142_mcp_calls.jsonl
|
||||
- one JSON line per MCP call
|
||||
- include tool name, arguments, success or failure, and raw result or raw error
|
||||
|
||||
4. temp/v04142_before_audit.json
|
||||
- raw before-state audit outputs
|
||||
|
||||
5. temp/v04142_after_audit.json
|
||||
- raw after-state audit outputs
|
||||
|
||||
6. temp/v04142_edit_actions.json
|
||||
- exact live edits attempted and whether each succeeded
|
||||
|
||||
7. If blocked, replace missing success artifacts with:
|
||||
- temp/v04142_blocker_evidence.json
|
||||
- include the exact failing step, exact raw response, and why the sprint could not proceed
|
||||
|
||||
Required validation actions:
|
||||
- python -m py_compile on every changed Python file
|
||||
- relevant tests for touched runtime/MCP/coherence code
|
||||
- live MCP calls against the open project
|
||||
- before and after audits on the same open project session
|
||||
|
||||
## Context
|
||||
|
||||
Read these first:
|
||||
|
||||
- previous failed run directory:
|
||||
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-195932-queue
|
||||
- previous summary:
|
||||
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-195932-queue\SUMMARY.md
|
||||
- previous reviews:
|
||||
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-195932-queue\reviews\codex_master_pre_fix.json
|
||||
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-195932-queue\reviews\opencode_qwen3coder_plus.json
|
||||
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-195932-queue\reviews\opencode_glm47.json
|
||||
- previous validation report:
|
||||
- docs/SPRINT_v0.1.41_VALIDATION_REPORT.md
|
||||
|
||||
Primary source files:
|
||||
- AbletonMCP_AI/MCP_Server/server.py
|
||||
- AbletonMCP_AI/MCP_Server/sample_selector.py
|
||||
- abletonmcp_init.py
|
||||
- AbletonMCP_AI/abletonmcp_runtime.py
|
||||
|
||||
Execution priority for this sprint:
|
||||
1. prove live target and MCP connectivity
|
||||
2. validate existing tools live
|
||||
3. minimally fix only the runtime blockers that prevent validation
|
||||
4. apply one bounded real edit pass
|
||||
5. capture before/after evidence
|
||||
6. report strictly from repository truth
|
||||
|
||||
Do not overclaim. This sprint exists to turn unvalidated open-project editing infrastructure into demonstrated live behavior on the real Ableton session.
|
||||
@@ -0,0 +1,163 @@
|
||||
# SPRINT v0.1.42 - Live Proof of Open-Project Editing
|
||||
|
||||
## Goal
|
||||
|
||||
Validate and use the existing MCP editing path on the already-open Ableton project:
|
||||
|
||||
- C:\Users\ren\Desktop\song Project\song.als
|
||||
|
||||
This sprint is not a generation sprint and not a wrapper-expansion sprint.
|
||||
|
||||
It is a runtime-proof sprint:
|
||||
|
||||
- prove the MCP can inspect the real open project
|
||||
- prove the MCP can apply real Arrangement edits on that project
|
||||
- prove the edits improve coherence in measurable ways
|
||||
- document exact evidence or fail explicitly
|
||||
|
||||
## Why This Task Exists
|
||||
|
||||
The previous run failed.
|
||||
|
||||
Repository truth from the failed run:
|
||||
|
||||
- infrastructure was added in AbletonMCP_AI/MCP_Server/server.py
|
||||
- contextual snare scoring was added in AbletonMCP_AI/MCP_Server/sample_selector.py
|
||||
- docs/SPRINT_v0.1.41_VALIDATION_REPORT.md was corrected to an honest fail
|
||||
- no live MCP calls were exercised against song.als
|
||||
- no real edit pass was applied
|
||||
- no before/after coherence metrics were captured
|
||||
- no proof exists that the new editing tools work through the real Ableton runtime path
|
||||
|
||||
The highest-signal blocker is now obvious:
|
||||
|
||||
- stop adding unvalidated infrastructure
|
||||
- prove runtime behavior on the open project
|
||||
- if runtime behavior is blocked, isolate the exact blocker with raw evidence
|
||||
|
||||
## Required Work
|
||||
|
||||
1. Prove the live target is the real open project.
|
||||
- Connect through the MCP to the active Ableton session.
|
||||
- Capture enough session evidence to prove the target is the already-open song.als session and not a generated set or blank template.
|
||||
- Save raw outputs under temp/.
|
||||
|
||||
2. Validate the existing MCP tool path live before adding more tools.
|
||||
- Exercise these tools against the open project and record exact calls plus raw outputs:
|
||||
- get_session_info()
|
||||
- get_tracks()
|
||||
- get_track_info(...)
|
||||
- get_clips(...)
|
||||
- get_clip_info(...)
|
||||
- get_devices(...)
|
||||
- get_device_parameters(...)
|
||||
- set_device_parameter_by_name(...)
|
||||
- one arrangement creation or duplication path
|
||||
- one arrangement MIDI note insertion path
|
||||
- For each tool, classify it as:
|
||||
- live validated
|
||||
- failed live
|
||||
- blocked by backend/runtime limitation
|
||||
- Do not mark a tool complete just because it compiles.
|
||||
|
||||
3. If a live tool path fails, fix only the minimal blocker.
|
||||
- Use the existing code added in v0.1.41 as the starting point.
|
||||
- If a backend handler or runtime path is missing, add the smallest fix needed in the real runtime path.
|
||||
- Re-run the exact same MCP call after the fix and save the before/after evidence.
|
||||
- Do not add unrelated new feature surface.
|
||||
|
||||
4. Audit the open project before editing.
|
||||
- Run project-facing audits on the real song.als session.
|
||||
- Capture exact before metrics for:
|
||||
- silence islands
|
||||
- mirrored section pairs
|
||||
- harmonic coverage / harmonic backbone status
|
||||
- same-source dominance or repeated-source overuse
|
||||
- repeated clip overuse if available
|
||||
- Save raw outputs under temp/.
|
||||
- Use these audits to choose the edit targets.
|
||||
|
||||
5. Apply a bounded real edit pass on song.als.
|
||||
- Use the validated MCP editing tools on the real open project.
|
||||
- The edit pass must be small, targeted, and measurable.
|
||||
- Prioritize:
|
||||
- extending or repairing harmonic MIDI backbone in Arrangement
|
||||
- reducing dead gaps
|
||||
- breaking at least one mirrored or obviously repeated arrangement shape
|
||||
- improving continuity without destroying identity
|
||||
- The harmonic MIDI backbone must become real Arrangement content intended to be audible.
|
||||
- It is acceptable to use keys, pluck, pad, or synth timbre.
|
||||
- It is not acceptable to leave the backbone as metadata, a hidden clip, or an empty placeholder.
|
||||
|
||||
6. Validate snare selectivity in the real path.
|
||||
- Do not hard-ban SS_RNBL_Me_Gustas_One_Shot_Snare.wav.
|
||||
- Prove whether the new contextual snare scoring actually affects selection in real use.
|
||||
- If the logic exists but is not wired into the live path, wire the minimal real path and validate it.
|
||||
- Record exact evidence showing whether lower-energy sections are treated more conservatively than higher-energy sections.
|
||||
|
||||
7. Re-audit after editing.
|
||||
- Run the same project-facing audits again.
|
||||
- Save raw after-state outputs under temp/.
|
||||
- Produce an exact before/after table using measured values, not estimates.
|
||||
- State clearly what improved, what stayed flat, and what regressed.
|
||||
|
||||
8. Keep the scope disciplined.
|
||||
- Touch only the files directly required to validate or minimally fix the runtime editing path.
|
||||
- Likely files:
|
||||
- AbletonMCP_AI/MCP_Server/server.py
|
||||
- AbletonMCP_AI/MCP_Server/sample_selector.py
|
||||
- abletonmcp_init.py
|
||||
- AbletonMCP_AI/abletonmcp_runtime.py
|
||||
- docs/SPRINT_v0.1.42_VALIDATION_REPORT.md
|
||||
- Do not add broad new capability areas unless they are the direct blocker to a required live validation step.
|
||||
|
||||
9. Fail fast if the runtime is not reachable.
|
||||
- If Ableton, the control surface, or the MCP connection is unavailable, do not continue building infrastructure.
|
||||
- Capture the exact failing step and raw evidence.
|
||||
- Mark the sprint failed with specific blocker evidence.
|
||||
- A code-only partial success is not acceptable for this sprint.
|
||||
|
||||
## Validation
|
||||
|
||||
Produce all of the following artifacts:
|
||||
|
||||
1. docs/SPRINT_v0.1.42_VALIDATION_REPORT.md
|
||||
Required sections:
|
||||
- Summary
|
||||
- Files Changed
|
||||
- Live Target Proof
|
||||
- MCP Tools Validated Live
|
||||
- Project Audits Before
|
||||
- Project Edits Applied
|
||||
- Project Audits After
|
||||
- Before/After Metrics
|
||||
- Snare Selectivity
|
||||
- Harmonic MIDI Backbone
|
||||
- What Is Still Weak
|
||||
- Remaining Risks
|
||||
|
||||
2. temp/v04142_live_target_proof.json
|
||||
- raw evidence proving the active Ableton session is the intended open project
|
||||
|
||||
3. temp/v04142_mcp_calls.jsonl
|
||||
- one JSON line per MCP call
|
||||
- include tool name, arguments, success or failure, and raw result or raw error
|
||||
|
||||
4. temp/v04142_before_audit.json
|
||||
- raw before-state audit outputs
|
||||
|
||||
5. temp/v04142_after_audit.json
|
||||
- raw after-state audit outputs
|
||||
|
||||
6. temp/v04142_edit_actions.json
|
||||
- exact live edits attempted and whether each succeeded
|
||||
|
||||
7. If blocked, replace missing success artifacts with:
|
||||
- temp/v04142_blocker_evidence.json
|
||||
- include the exact failing step, exact raw response, and why the sprint could not proceed
|
||||
|
||||
Required validation actions:
|
||||
- python -m py_compile on every changed Python file
|
||||
- relevant tests for touched runtime/MCP/coherence code
|
||||
- live MCP calls against the open project
|
||||
- before and after audits on the same open project session
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"id": "20260403-203814-20260403-203809-sprint-v0-1-42-live-proof-of-ope",
|
||||
"title": "20260403-203809-sprint-v0-1-42-live-proof-of-open-project-editin",
|
||||
"state": "failed",
|
||||
"finished_at": "2026-04-03T21:14:24.1239242-03:00",
|
||||
"source": "C:\\ProgramData\\Ableton\\Live 12 Suite\\Resources\\MIDI Remote Scripts\\ralph\\tasks\\inbox\\20260403-203809-sprint-v0-1-42-live-proof-of-open-project-editin",
|
||||
"run_id": "",
|
||||
"summary": "Start-RalphAutopilot.ps1 failed with exit code 1"
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
This sprint passes only if all of the following are true:
|
||||
|
||||
1. The live target is the already-open song.als session.
|
||||
2. The run makes at least one real source-code change in the runtime or selection path, unless an already-existing code path is conclusively shown to be the canonical supported fallback and is validated end-to-end live.
|
||||
3. At least one real Arrangement content edit is applied on the open project to improve harmonic continuity or fill a real weak span.
|
||||
4. The backbone goal is met either by:
|
||||
- meaningful Arrangement MIDI backbone content added live
|
||||
- or a documented and validated supported fallback path that adds backbone-like Arrangement content when MIDI insertion is blocked
|
||||
5. At least one coherence metric in the saved before/after evidence improves materially.
|
||||
6. The run validates the previously missing live tool coverage:
|
||||
- get_tracks()
|
||||
- get_device_parameters(...)
|
||||
- set_device_parameter_by_name(...)
|
||||
7. Snare selectivity is validated through a real runtime path across at least two section contexts.
|
||||
8. All changed Python files compile.
|
||||
9. Relevant tests for touched code pass.
|
||||
10. The validation report contains exact raw evidence references and does not overclaim.
|
||||
11. Codex can reasonably return pass from repository evidence alone.
|
||||
|
||||
Automatic fail conditions:
|
||||
- no source-code or real wiring change is made while blockers remain
|
||||
- only property edits are applied again
|
||||
- backbone remains absent and no validated fallback is delivered
|
||||
- all saved coherence metrics remain flat again
|
||||
- snare selectivity is still argued from inference instead of runtime evidence
|
||||
- the report claims success without a measurable improvement
|
||||
@@ -0,0 +1,34 @@
|
||||
Read these first:
|
||||
|
||||
- previous run directory:
|
||||
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-203815-queue
|
||||
- previous summary:
|
||||
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-203815-queue\SUMMARY.md
|
||||
- previous reviews:
|
||||
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-203815-queue\reviews\codex_master_pre_fix.json
|
||||
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-203815-queue\reviews\opencode_qwen3coder_plus.json
|
||||
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-203815-queue\reviews\opencode_glm47.json
|
||||
- previous validation report:
|
||||
- docs/SPRINT_v0.1.42_VALIDATION_REPORT.md
|
||||
|
||||
Most important evidence files from the previous run:
|
||||
- temp/v04142_mcp_calls_final.jsonl
|
||||
- temp/v04142_comprehensive_validation.json
|
||||
- temp/v04142_audio_pattern_results.json
|
||||
- temp/v04142_edit_actions.json
|
||||
|
||||
Primary source files:
|
||||
- AbletonMCP_AI/MCP_Server/server.py
|
||||
- abletonmcp_init.py
|
||||
- AbletonMCP_AI/abletonmcp_runtime.py
|
||||
- AbletonMCP_AI/MCP_Server/sample_selector.py
|
||||
|
||||
Execution priority for this sprint:
|
||||
1. preserve the proven live connection and working arrangement-audio path
|
||||
2. make the blocked backbone path succeed, or formalize a supported fallback in code
|
||||
3. validate the previously missing live MCP tools
|
||||
4. deliver one measurable coherence improvement
|
||||
5. validate snare selectivity in a real runtime path
|
||||
6. report only what repository evidence proves
|
||||
|
||||
Do not overclaim. The previous run proved that live editing is partially possible. This sprint exists to turn that partial proof into a repeatable, code-backed, measurable project improvement.
|
||||
@@ -0,0 +1,238 @@
|
||||
# SPRINT v0.1.43 - Unlock Backbone Editing and Measurable Coherence Gains
|
||||
|
||||
## Goal
|
||||
|
||||
Use the live MCP connection on the already-open Ableton project:
|
||||
|
||||
- C:\Users\ren\Desktop\song Project\song.als
|
||||
|
||||
to close the remaining blocker from v0.1.42:
|
||||
|
||||
- convert the currently proven live inspection and audio-pattern editing path into a path that produces a meaningful harmonic backbone improvement and at least one measurable coherence improvement
|
||||
|
||||
This sprint is not about proving connectivity again in isolation.
|
||||
|
||||
This sprint is about:
|
||||
- fixing or formalizing the blocked backbone-edit path
|
||||
- using that path on the live project
|
||||
- producing measurable before/after improvement
|
||||
- validating snare selectivity in a real runtime path
|
||||
|
||||
## Why This Task Exists
|
||||
|
||||
Repository truth from v0.1.42:
|
||||
|
||||
- the run did reach the correct open project
|
||||
- real MCP-driven Arrangement audio edits succeeded through `create_arrangement_audio_pattern`
|
||||
- no source-code fix was made
|
||||
- the harmonic MIDI backbone requirement was still not met
|
||||
- key coherence metrics did not materially improve
|
||||
- snare selectivity was still inferential rather than proven in a real selection path
|
||||
|
||||
High-signal truths from the run artifacts:
|
||||
- `temp/v04142_mcp_calls_final.jsonl` proves `create_arrangement_audio_pattern` works live
|
||||
- `temp/v04142_comprehensive_validation.json` shows 3 arrangement audio-pattern edits succeeded
|
||||
- the same artifact shows mirrored pairs stayed at 100 and clip overuse stayed high
|
||||
- `docs/SPRINT_v0.1.42_VALIDATION_REPORT.md` admits MIDI note insertion is still blocked
|
||||
- the worktree had no source-code diff, so the blocker was diagnosed but not fixed
|
||||
|
||||
The next step is therefore not “more validation.â€
|
||||
It is:
|
||||
- implement the smallest real code fix or formal runtime fallback needed to make backbone extension and coherence improvement repeatable
|
||||
- then prove it on song.als with exact metrics
|
||||
|
||||
## Required Work
|
||||
|
||||
1. Start from the live path that actually worked in v0.1.42.
|
||||
- Reuse the live MCP connection approach that already validated the real open `song.als` session.
|
||||
- Reuse the proven `create_arrangement_audio_pattern` path if MIDI editing remains blocked.
|
||||
- Do not regress the working runtime path.
|
||||
|
||||
2. Resolve the backbone-edit blocker at code level.
|
||||
- You must make a real code change in the runtime path this sprint unless the existing code already supports a better fallback and only wiring is missing.
|
||||
- Priority order:
|
||||
1. make a meaningful Arrangement MIDI backbone edit succeed
|
||||
2. if that is genuinely blocked by the Live API, implement and validate a formal fallback path that creates backbone-like Arrangement content through a supported method
|
||||
- A valid fallback is not random content.
|
||||
- A valid fallback must:
|
||||
- be intentional
|
||||
- extend harmonic continuity
|
||||
- be audibly useful
|
||||
- be documented as the canonical path when MIDI insertion is blocked
|
||||
|
||||
3. Prove the fallback or fix is real on the live project.
|
||||
- Apply at least one backbone-oriented Arrangement edit that increases continuity in a musically relevant span.
|
||||
- The edit must target a real gap, weak span, or dead tail in song.als.
|
||||
- The edit must not be only track property changes.
|
||||
- It must be actual Arrangement content.
|
||||
|
||||
4. Require at least one measurable coherence improvement.
|
||||
- Before editing, capture exact metrics.
|
||||
- After editing, capture exact metrics again.
|
||||
- At least one of these must improve in the saved evidence:
|
||||
- silence islands
|
||||
- mirrored section pairs
|
||||
- harmonic coverage/backbone presence
|
||||
- same-source dominance
|
||||
- repeated clip overuse
|
||||
- “3 patterns created†is not enough if the saved coherence metrics remain flat.
|
||||
|
||||
5. Validate the missing live MCP tools from v0.1.42.
|
||||
- The previous run still under-validated the tool set.
|
||||
- This sprint must exercise and record exact results for:
|
||||
- get_tracks()
|
||||
- get_device_parameters(...)
|
||||
- set_device_parameter_by_name(...)
|
||||
- Also re-confirm one arrangement creation/edit path and one audit path.
|
||||
- If a tool is blocked, record the exact raw blocker and the exact fallback.
|
||||
|
||||
6. Validate snare selectivity in a real runtime path.
|
||||
- Do not infer from the current project state.
|
||||
- Do not cite older sprint text as proof.
|
||||
- Run a real runtime path that exercises the selection logic or the relevant selection entry point.
|
||||
- Record exact evidence showing whether the aggressive snare is penalized differently across at least two different section-energy contexts.
|
||||
- If the scoring exists but is not wired into the runtime path, wire the minimum real path and validate it.
|
||||
|
||||
7. Keep scope tight and senior.
|
||||
- Do not add broad new feature surfaces.
|
||||
- Do not rewrite the generation system.
|
||||
- Touch only the files required to:
|
||||
- unlock the blocked edit path
|
||||
- validate the missing tool coverage
|
||||
- make the coherence improvement measurable
|
||||
- Likely candidates:
|
||||
- AbletonMCP_AI/MCP_Server/server.py
|
||||
- abletonmcp_init.py
|
||||
- AbletonMCP_AI/abletonmcp_runtime.py
|
||||
- AbletonMCP_AI/MCP_Server/sample_selector.py
|
||||
- docs/SPRINT_v0.1.43_VALIDATION_REPORT.md
|
||||
|
||||
8. Fail honestly if the blocker is fundamental and unfixed.
|
||||
- If the MIDI path remains fundamentally blocked, the report must say so explicitly.
|
||||
- But in that case the sprint still must either:
|
||||
- ship a real validated fallback path with measurable improvement
|
||||
- or fail
|
||||
- A documentation-only explanation is not enough anymore.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
This sprint passes only if all of the following are true:
|
||||
|
||||
1. The live target is the already-open song.als session.
|
||||
2. The run makes at least one real source-code change in the runtime or selection path, unless an already-existing code path is conclusively shown to be the canonical supported fallback and is validated end-to-end live.
|
||||
3. At least one real Arrangement content edit is applied on the open project to improve harmonic continuity or fill a real weak span.
|
||||
4. The backbone goal is met either by:
|
||||
- meaningful Arrangement MIDI backbone content added live
|
||||
- or a documented and validated supported fallback path that adds backbone-like Arrangement content when MIDI insertion is blocked
|
||||
5. At least one coherence metric in the saved before/after evidence improves materially.
|
||||
6. The run validates the previously missing live tool coverage:
|
||||
- get_tracks()
|
||||
- get_device_parameters(...)
|
||||
- set_device_parameter_by_name(...)
|
||||
7. Snare selectivity is validated through a real runtime path across at least two section contexts.
|
||||
8. All changed Python files compile.
|
||||
9. Relevant tests for touched code pass.
|
||||
10. The validation report contains exact raw evidence references and does not overclaim.
|
||||
11. Codex can reasonably return pass from repository evidence alone.
|
||||
|
||||
Automatic fail conditions:
|
||||
- no source-code or real wiring change is made while blockers remain
|
||||
- only property edits are applied again
|
||||
- backbone remains absent and no validated fallback is delivered
|
||||
- all saved coherence metrics remain flat again
|
||||
- snare selectivity is still argued from inference instead of runtime evidence
|
||||
- the report claims success without a measurable improvement
|
||||
|
||||
## Validation
|
||||
|
||||
Produce all of the following artifacts:
|
||||
|
||||
1. docs/SPRINT_v0.1.43_VALIDATION_REPORT.md
|
||||
Required sections:
|
||||
- Summary
|
||||
- Files Changed
|
||||
- Live Target Proof
|
||||
- Runtime Fix or Canonical Fallback
|
||||
- MCP Tools Validated Live
|
||||
- Project Audits Before
|
||||
- Project Edits Applied
|
||||
- Project Audits After
|
||||
- Before/After Metrics
|
||||
- Snare Selectivity
|
||||
- Harmonic Backbone Outcome
|
||||
- What Is Still Weak
|
||||
- Remaining Risks
|
||||
|
||||
2. temp/v04143_live_target_proof.json
|
||||
- raw proof that the active session is the intended open project
|
||||
|
||||
3. temp/v04143_mcp_calls.jsonl
|
||||
- one JSON line per MCP call
|
||||
- include tool name, arguments, success/failure, and raw result/error
|
||||
|
||||
4. temp/v04143_before_audit.json
|
||||
- raw before-state audit outputs
|
||||
|
||||
5. temp/v04143_after_audit.json
|
||||
- raw after-state audit outputs
|
||||
|
||||
6. temp/v04143_edit_actions.json
|
||||
- exact live edits attempted and whether each succeeded
|
||||
|
||||
7. temp/v04143_snare_selectivity_validation.json
|
||||
- runtime evidence for at least two section contexts
|
||||
- include the exact sample candidates or scoring evidence used
|
||||
|
||||
8. temp/v04143_blocker_or_fallback.json
|
||||
- if MIDI remains blocked, document:
|
||||
- exact failing call
|
||||
- exact raw response
|
||||
- exact supported fallback used instead
|
||||
- proof that the fallback was applied live
|
||||
|
||||
9. temp/v04143_metric_delta.json
|
||||
- explicit before/after delta summary for the coherence metrics
|
||||
|
||||
Required validation actions:
|
||||
- python -m py_compile on every changed Python file
|
||||
- relevant tests for every touched runtime/MCP/selection file
|
||||
- live MCP calls against the open project
|
||||
- before and after audits from the same live session
|
||||
- exact evidence for either a fixed backbone path or a validated fallback path
|
||||
|
||||
## Context
|
||||
|
||||
Read these first:
|
||||
|
||||
- previous run directory:
|
||||
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-203815-queue
|
||||
- previous summary:
|
||||
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-203815-queue\SUMMARY.md
|
||||
- previous reviews:
|
||||
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-203815-queue\reviews\codex_master_pre_fix.json
|
||||
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-203815-queue\reviews\opencode_qwen3coder_plus.json
|
||||
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-203815-queue\reviews\opencode_glm47.json
|
||||
- previous validation report:
|
||||
- docs/SPRINT_v0.1.42_VALIDATION_REPORT.md
|
||||
|
||||
Most important evidence files from the previous run:
|
||||
- temp/v04142_mcp_calls_final.jsonl
|
||||
- temp/v04142_comprehensive_validation.json
|
||||
- temp/v04142_audio_pattern_results.json
|
||||
- temp/v04142_edit_actions.json
|
||||
|
||||
Primary source files:
|
||||
- AbletonMCP_AI/MCP_Server/server.py
|
||||
- abletonmcp_init.py
|
||||
- AbletonMCP_AI/abletonmcp_runtime.py
|
||||
- AbletonMCP_AI/MCP_Server/sample_selector.py
|
||||
|
||||
Execution priority for this sprint:
|
||||
1. preserve the proven live connection and working arrangement-audio path
|
||||
2. make the blocked backbone path succeed, or formalize a supported fallback in code
|
||||
3. validate the previously missing live MCP tools
|
||||
4. deliver one measurable coherence improvement
|
||||
5. validate snare selectivity in a real runtime path
|
||||
6. report only what repository evidence proves
|
||||
|
||||
Do not overclaim. The previous run proved that live editing is partially possible. This sprint exists to turn that partial proof into a repeatable, code-backed, measurable project improvement.
|
||||
@@ -0,0 +1,172 @@
|
||||
# SPRINT v0.1.43 - Unlock Backbone Editing and Measurable Coherence Gains
|
||||
|
||||
## Goal
|
||||
|
||||
Use the live MCP connection on the already-open Ableton project:
|
||||
|
||||
- C:\Users\ren\Desktop\song Project\song.als
|
||||
|
||||
to close the remaining blocker from v0.1.42:
|
||||
|
||||
- convert the currently proven live inspection and audio-pattern editing path into a path that produces a meaningful harmonic backbone improvement and at least one measurable coherence improvement
|
||||
|
||||
This sprint is not about proving connectivity again in isolation.
|
||||
|
||||
This sprint is about:
|
||||
- fixing or formalizing the blocked backbone-edit path
|
||||
- using that path on the live project
|
||||
- producing measurable before/after improvement
|
||||
- validating snare selectivity in a real runtime path
|
||||
|
||||
## Why This Task Exists
|
||||
|
||||
Repository truth from v0.1.42:
|
||||
|
||||
- the run did reach the correct open project
|
||||
- real MCP-driven Arrangement audio edits succeeded through `create_arrangement_audio_pattern`
|
||||
- no source-code fix was made
|
||||
- the harmonic MIDI backbone requirement was still not met
|
||||
- key coherence metrics did not materially improve
|
||||
- snare selectivity was still inferential rather than proven in a real selection path
|
||||
|
||||
High-signal truths from the run artifacts:
|
||||
- `temp/v04142_mcp_calls_final.jsonl` proves `create_arrangement_audio_pattern` works live
|
||||
- `temp/v04142_comprehensive_validation.json` shows 3 arrangement audio-pattern edits succeeded
|
||||
- the same artifact shows mirrored pairs stayed at 100 and clip overuse stayed high
|
||||
- `docs/SPRINT_v0.1.42_VALIDATION_REPORT.md` admits MIDI note insertion is still blocked
|
||||
- the worktree had no source-code diff, so the blocker was diagnosed but not fixed
|
||||
|
||||
The next step is therefore not “more validation.â€
|
||||
It is:
|
||||
- implement the smallest real code fix or formal runtime fallback needed to make backbone extension and coherence improvement repeatable
|
||||
- then prove it on song.als with exact metrics
|
||||
|
||||
## Required Work
|
||||
|
||||
1. Start from the live path that actually worked in v0.1.42.
|
||||
- Reuse the live MCP connection approach that already validated the real open `song.als` session.
|
||||
- Reuse the proven `create_arrangement_audio_pattern` path if MIDI editing remains blocked.
|
||||
- Do not regress the working runtime path.
|
||||
|
||||
2. Resolve the backbone-edit blocker at code level.
|
||||
- You must make a real code change in the runtime path this sprint unless the existing code already supports a better fallback and only wiring is missing.
|
||||
- Priority order:
|
||||
1. make a meaningful Arrangement MIDI backbone edit succeed
|
||||
2. if that is genuinely blocked by the Live API, implement and validate a formal fallback path that creates backbone-like Arrangement content through a supported method
|
||||
- A valid fallback is not random content.
|
||||
- A valid fallback must:
|
||||
- be intentional
|
||||
- extend harmonic continuity
|
||||
- be audibly useful
|
||||
- be documented as the canonical path when MIDI insertion is blocked
|
||||
|
||||
3. Prove the fallback or fix is real on the live project.
|
||||
- Apply at least one backbone-oriented Arrangement edit that increases continuity in a musically relevant span.
|
||||
- The edit must target a real gap, weak span, or dead tail in song.als.
|
||||
- The edit must not be only track property changes.
|
||||
- It must be actual Arrangement content.
|
||||
|
||||
4. Require at least one measurable coherence improvement.
|
||||
- Before editing, capture exact metrics.
|
||||
- After editing, capture exact metrics again.
|
||||
- At least one of these must improve in the saved evidence:
|
||||
- silence islands
|
||||
- mirrored section pairs
|
||||
- harmonic coverage/backbone presence
|
||||
- same-source dominance
|
||||
- repeated clip overuse
|
||||
- “3 patterns created†is not enough if the saved coherence metrics remain flat.
|
||||
|
||||
5. Validate the missing live MCP tools from v0.1.42.
|
||||
- The previous run still under-validated the tool set.
|
||||
- This sprint must exercise and record exact results for:
|
||||
- get_tracks()
|
||||
- get_device_parameters(...)
|
||||
- set_device_parameter_by_name(...)
|
||||
- Also re-confirm one arrangement creation/edit path and one audit path.
|
||||
- If a tool is blocked, record the exact raw blocker and the exact fallback.
|
||||
|
||||
6. Validate snare selectivity in a real runtime path.
|
||||
- Do not infer from the current project state.
|
||||
- Do not cite older sprint text as proof.
|
||||
- Run a real runtime path that exercises the selection logic or the relevant selection entry point.
|
||||
- Record exact evidence showing whether the aggressive snare is penalized differently across at least two different section-energy contexts.
|
||||
- If the scoring exists but is not wired into the runtime path, wire the minimum real path and validate it.
|
||||
|
||||
7. Keep scope tight and senior.
|
||||
- Do not add broad new feature surfaces.
|
||||
- Do not rewrite the generation system.
|
||||
- Touch only the files required to:
|
||||
- unlock the blocked edit path
|
||||
- validate the missing tool coverage
|
||||
- make the coherence improvement measurable
|
||||
- Likely candidates:
|
||||
- AbletonMCP_AI/MCP_Server/server.py
|
||||
- abletonmcp_init.py
|
||||
- AbletonMCP_AI/abletonmcp_runtime.py
|
||||
- AbletonMCP_AI/MCP_Server/sample_selector.py
|
||||
- docs/SPRINT_v0.1.43_VALIDATION_REPORT.md
|
||||
|
||||
8. Fail honestly if the blocker is fundamental and unfixed.
|
||||
- If the MIDI path remains fundamentally blocked, the report must say so explicitly.
|
||||
- But in that case the sprint still must either:
|
||||
- ship a real validated fallback path with measurable improvement
|
||||
- or fail
|
||||
- A documentation-only explanation is not enough anymore.
|
||||
|
||||
## Validation
|
||||
|
||||
Produce all of the following artifacts:
|
||||
|
||||
1. docs/SPRINT_v0.1.43_VALIDATION_REPORT.md
|
||||
Required sections:
|
||||
- Summary
|
||||
- Files Changed
|
||||
- Live Target Proof
|
||||
- Runtime Fix or Canonical Fallback
|
||||
- MCP Tools Validated Live
|
||||
- Project Audits Before
|
||||
- Project Edits Applied
|
||||
- Project Audits After
|
||||
- Before/After Metrics
|
||||
- Snare Selectivity
|
||||
- Harmonic Backbone Outcome
|
||||
- What Is Still Weak
|
||||
- Remaining Risks
|
||||
|
||||
2. temp/v04143_live_target_proof.json
|
||||
- raw proof that the active session is the intended open project
|
||||
|
||||
3. temp/v04143_mcp_calls.jsonl
|
||||
- one JSON line per MCP call
|
||||
- include tool name, arguments, success/failure, and raw result/error
|
||||
|
||||
4. temp/v04143_before_audit.json
|
||||
- raw before-state audit outputs
|
||||
|
||||
5. temp/v04143_after_audit.json
|
||||
- raw after-state audit outputs
|
||||
|
||||
6. temp/v04143_edit_actions.json
|
||||
- exact live edits attempted and whether each succeeded
|
||||
|
||||
7. temp/v04143_snare_selectivity_validation.json
|
||||
- runtime evidence for at least two section contexts
|
||||
- include the exact sample candidates or scoring evidence used
|
||||
|
||||
8. temp/v04143_blocker_or_fallback.json
|
||||
- if MIDI remains blocked, document:
|
||||
- exact failing call
|
||||
- exact raw response
|
||||
- exact supported fallback used instead
|
||||
- proof that the fallback was applied live
|
||||
|
||||
9. temp/v04143_metric_delta.json
|
||||
- explicit before/after delta summary for the coherence metrics
|
||||
|
||||
Required validation actions:
|
||||
- python -m py_compile on every changed Python file
|
||||
- relevant tests for every touched runtime/MCP/selection file
|
||||
- live MCP calls against the open project
|
||||
- before and after audits from the same live session
|
||||
- exact evidence for either a fixed backbone path or a validated fallback path
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"id": "20260403-211425-20260403-211423-sprint-v0-1-43-unlock-backbone-e",
|
||||
"title": "20260403-211423-sprint-v0-1-43-unlock-backbone-editing-and-measu",
|
||||
"state": "failed",
|
||||
"finished_at": "2026-04-03T21:28:37.1850500-03:00",
|
||||
"source": "C:\\ProgramData\\Ableton\\Live 12 Suite\\Resources\\MIDI Remote Scripts\\ralph\\tasks\\inbox\\20260403-211423-sprint-v0-1-43-unlock-backbone-editing-and-measu",
|
||||
"run_id": "",
|
||||
"summary": "Start-RalphAutopilot.ps1 failed with exit code 1"
|
||||
}
|
||||
1
ralph/tasks/inbox/.gitkeep
Normal file
1
ralph/tasks/inbox/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
ralph/tasks/processing/.gitkeep
Normal file
1
ralph/tasks/processing/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
9
ralph/templates/ACCEPTANCE.md
Normal file
9
ralph/templates/ACCEPTANCE.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Acceptance
|
||||
|
||||
The task is only complete if all of these are true:
|
||||
|
||||
1. implementation compiles
|
||||
2. relevant tests pass
|
||||
3. runtime evidence exists if the task touches Live or MCP integration
|
||||
4. no inflated claims in docs
|
||||
5. remaining risks are written explicitly
|
||||
35
ralph/templates/CODEX_NEXT_TASK_PROMPT.md
Normal file
35
ralph/templates/CODEX_NEXT_TASK_PROMPT.md
Normal file
@@ -0,0 +1,35 @@
|
||||
Write the next autonomous Ralph task as a single Markdown document.
|
||||
|
||||
Requirements:
|
||||
|
||||
- Output only the Markdown task document, with no preface and no code fences.
|
||||
- This task is for Ralph swarm automation, not a human chat response.
|
||||
- The task must be actionable, specific, and based only on repository truth and run artifacts.
|
||||
- The task must continue the current line of development.
|
||||
- Prefer runtime validation, exact evidence, and concrete acceptance criteria.
|
||||
- If the previous run failed, focus on the highest-signal blockers first.
|
||||
- If the previous run succeeded, focus on the next most valuable improvement.
|
||||
- Keep the task self-contained so it can be submitted directly into the inbox.
|
||||
|
||||
Required Markdown structure:
|
||||
|
||||
# <short title>
|
||||
|
||||
## Goal
|
||||
|
||||
## Why This Task Exists
|
||||
|
||||
## Required Work
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
## Validation
|
||||
|
||||
## Context
|
||||
|
||||
Rules:
|
||||
|
||||
- Be strict and senior.
|
||||
- Do not overclaim completion.
|
||||
- Do not ask for manual handoff or manual reasoning from the operator.
|
||||
- Assume Ralph will run this automatically.
|
||||
41
ralph/templates/CODEX_REVIEW_PROMPT.md
Normal file
41
ralph/templates/CODEX_REVIEW_PROMPT.md
Normal file
@@ -0,0 +1,41 @@
|
||||
You are Codex running as the persistent master reviewer for this repository.
|
||||
|
||||
Read the task pack, reviewer outputs and current worktree diff.
|
||||
|
||||
Your job:
|
||||
|
||||
- identify the real state of the implementation
|
||||
- separate infrastructure from validated integration
|
||||
- point out the highest-risk issues
|
||||
- say whether the task meets acceptance or not
|
||||
- write the next sprint if the task is not fully complete
|
||||
|
||||
Do not overclaim. Use the repository truth, not the worker's self-report.
|
||||
|
||||
Output rules:
|
||||
|
||||
- Output exactly one JSON object and nothing else.
|
||||
- Do not wrap the JSON in markdown fences.
|
||||
- Use this schema exactly:
|
||||
|
||||
{
|
||||
"verdict": "pass | needs_fix | fail",
|
||||
"acceptance_passed": true,
|
||||
"fix_required": false,
|
||||
"summary": "short single-paragraph summary",
|
||||
"highest_risk_issues": [
|
||||
{
|
||||
"severity": "high | medium | low",
|
||||
"title": "short issue title",
|
||||
"details": "one concise paragraph"
|
||||
}
|
||||
],
|
||||
"next_sprint_needed": false,
|
||||
"next_sprint_brief": "empty string if not needed"
|
||||
}
|
||||
|
||||
Verdict policy:
|
||||
|
||||
- Use `pass` only if the task truly satisfies acceptance and no blocking issue remains.
|
||||
- Use `needs_fix` if the implementation is promising but still misses acceptance or needs another implementer pass.
|
||||
- Use `fail` if the task is blocked, broken, or validated against the wrong runtime path.
|
||||
8
ralph/templates/CONTEXT.md
Normal file
8
ralph/templates/CONTEXT.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Context
|
||||
|
||||
List the canonical docs and code paths the worker must read first.
|
||||
|
||||
- active sprint doc
|
||||
- current handoff
|
||||
- relevant source files
|
||||
- known evidence files
|
||||
18
ralph/templates/IMPLEMENTER_PROMPT.md
Normal file
18
ralph/templates/IMPLEMENTER_PROMPT.md
Normal file
@@ -0,0 +1,18 @@
|
||||
You are the implementer.
|
||||
|
||||
Read the task pack files carefully and work only inside the assigned worktree.
|
||||
|
||||
Rules:
|
||||
|
||||
- use PowerShell and Windows paths
|
||||
- do not invent success
|
||||
- compile and run the smallest useful validation
|
||||
- write a short `CHANGES.md` in the run directory
|
||||
- if runtime validation is impossible, say so explicitly
|
||||
- do not edit outside the assigned worktree
|
||||
|
||||
Output:
|
||||
|
||||
- apply code changes
|
||||
- leave the worktree in a reviewable state
|
||||
- write a concise final summary
|
||||
15
ralph/templates/REVIEWER_PROMPT.md
Normal file
15
ralph/templates/REVIEWER_PROMPT.md
Normal file
@@ -0,0 +1,15 @@
|
||||
You are the reviewer.
|
||||
|
||||
Review the task pack and the implementer diff.
|
||||
|
||||
Primary focus:
|
||||
|
||||
- bugs
|
||||
- behavioral regressions
|
||||
- fake completion claims
|
||||
- missing tests
|
||||
- wrong assumptions about runtime behavior
|
||||
|
||||
Write findings first, ordered by severity.
|
||||
|
||||
Do not praise. Do not rewrite the task. Do not fix code unless explicitly asked.
|
||||
21
ralph/templates/TASK.md
Normal file
21
ralph/templates/TASK.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Task
|
||||
|
||||
## Goal
|
||||
|
||||
Describe the concrete engineering goal here.
|
||||
|
||||
## Files in scope
|
||||
|
||||
- list exact files
|
||||
|
||||
## Constraints
|
||||
|
||||
- keep Windows-native paths
|
||||
- validate with runtime when the task touches Ableton integration
|
||||
- do not declare success from compilation alone
|
||||
|
||||
## Expected output
|
||||
|
||||
- code changes
|
||||
- short changes report
|
||||
- remaining risks
|
||||
1
ralph/worktrees/.gitkeep
Normal file
1
ralph/worktrees/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
Reference in New Issue
Block a user