mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-02 13:39:41 +02:00
fix(terminal): linear-time link-provider regex; always allow blob workers in CSP
cmdPattern's empty-matchable unbounded arg group backtracked exponentially on wrapped heredoc/table lines — hovering one froze the tab for minutes. Non-empty tokens + bounded reps make it O(n); regression test extracts the shipped patterns and pins timing on the real killer shapes. worker-src 'self' blob: is now unconditional so terminal-ui's _safeYield tick worker (throttling escape) isn't CSP-blocked on non-gesture installs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,3 +8,4 @@
|
||||
export { RESEARCH_AGENT_PROMPT } from './research-agent.js';
|
||||
export { PLANNER_PROMPT } from './planner.js';
|
||||
export { PHASE_EXECUTION_PROMPT, TEAM_LEAD_PROMPT, REPLAN_PROMPT, SINGLE_TASK_PROMPT } from './orchestrator.js';
|
||||
export { RALPH_STATUS_CONTRACT, buildRalphLoopPrompt, type RalphLoopPromptOptions } from './ralph.js';
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* @fileoverview Ralph Loop prompt construction
|
||||
*
|
||||
* Builds the full `@ralph_prompt.md` content written for a new Ralph loop
|
||||
* session, including the RALPH_STATUS block contract. The contract travels
|
||||
* with the loop prompt (not the generated CLAUDE.md) so every Ralph session
|
||||
* emits parseable status blocks regardless of the project's CLAUDE.md.
|
||||
*
|
||||
* @module prompts/ralph
|
||||
*/
|
||||
|
||||
/**
|
||||
* Structured status-reporting contract appended to every Ralph loop prompt.
|
||||
*
|
||||
* `RalphStatusParser` (src/ralph-status-parser.ts) parses this block from
|
||||
* session output — keep the field names and enum values in sync with its
|
||||
* patterns.
|
||||
*/
|
||||
export const RALPH_STATUS_CONTRACT = `## Status Reporting
|
||||
|
||||
End EVERY response with exactly this block — Codeman parses it to track the loop:
|
||||
|
||||
\`\`\`
|
||||
---RALPH_STATUS---
|
||||
STATUS: IN_PROGRESS | COMPLETE | BLOCKED
|
||||
TASKS_COMPLETED_THIS_LOOP: <number>
|
||||
FILES_MODIFIED: <number>
|
||||
TESTS_STATUS: PASSING | FAILING | NOT_RUN
|
||||
WORK_TYPE: IMPLEMENTATION | TESTING | DOCUMENTATION | REFACTORING
|
||||
EXIT_SIGNAL: false | true
|
||||
RECOMMENDATION: <one line: what to do next>
|
||||
---END_RALPH_STATUS---
|
||||
\`\`\`
|
||||
|
||||
Rules:
|
||||
- \`EXIT_SIGNAL: true\` only when ALL tasks are verifiably done — then also output the completion phrase
|
||||
- \`STATUS: BLOCKED\` when you need human input; describe the blocker in RECOMMENDATION
|
||||
- Never set \`EXIT_SIGNAL: true\` while tests are failing
|
||||
`;
|
||||
|
||||
export interface RalphLoopPromptOptions {
|
||||
/** The user's task description (becomes the prompt header) */
|
||||
taskDescription: string;
|
||||
/** Completion phrase the session must emit inside <promise></promise> */
|
||||
completionPhrase: string;
|
||||
/** Whether a @fix_plan.md task plan was generated for this loop */
|
||||
hasPlan: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the full Ralph loop prompt written to `@ralph_prompt.md`.
|
||||
*/
|
||||
export function buildRalphLoopPrompt({ taskDescription, completionPhrase, hasPlan }: RalphLoopPromptOptions): string {
|
||||
let fullPrompt = taskDescription + '\n\n---\n\n';
|
||||
if (hasPlan) {
|
||||
fullPrompt += '## Task Plan\n\n';
|
||||
fullPrompt += 'A task plan has been written to `@fix_plan.md`. Use this to track progress:\n';
|
||||
fullPrompt += '- Reference the plan at the start of each iteration\n';
|
||||
fullPrompt += '- Update task checkboxes as you complete items\n';
|
||||
fullPrompt += '- Work through items in priority order (P0 > P1 > P2)\n\n';
|
||||
}
|
||||
fullPrompt += '## Iteration Protocol\n\n';
|
||||
fullPrompt += 'This is an autonomous loop. Files from previous iterations persist. On each iteration:\n';
|
||||
fullPrompt += '1. Check what work has already been done\n';
|
||||
fullPrompt += '2. Make incremental progress toward completion\n';
|
||||
fullPrompt += '3. Commit meaningful changes with descriptive messages\n\n';
|
||||
fullPrompt += '## Verification\n\n';
|
||||
fullPrompt += 'After each significant change:\n';
|
||||
fullPrompt += '- Run tests to verify (npm test, pytest, etc.)\n';
|
||||
fullPrompt += '- Check for type/lint errors if applicable\n';
|
||||
fullPrompt += '- If tests fail, read the error, fix it, and retry\n\n';
|
||||
fullPrompt += '## Completion Criteria\n\n';
|
||||
fullPrompt += `Output \`<promise>${completionPhrase}</promise>\` when ALL of the following are true:\n`;
|
||||
fullPrompt += '- All requirements from the task description are implemented\n';
|
||||
fullPrompt += '- All tests pass\n';
|
||||
fullPrompt += '- Changes are committed\n\n';
|
||||
fullPrompt += '## If Stuck\n\n';
|
||||
fullPrompt += 'If you encounter the same error for 3+ iterations:\n';
|
||||
fullPrompt += "1. Document what you've tried\n";
|
||||
fullPrompt += '2. Identify the specific blocker\n';
|
||||
fullPrompt += '3. Try an alternative approach\n';
|
||||
fullPrompt += '4. If truly blocked, output `<promise>BLOCKED</promise>` with an explanation\n\n';
|
||||
fullPrompt += RALPH_STATUS_CONTRACT;
|
||||
return fullPrompt;
|
||||
}
|
||||
@@ -56,42 +56,3 @@ This session is managed by Codeman and runs inside tmux (`CODEMAN_MUX=1` confirm
|
||||
- NEVER kill your own session: no `tmux kill-session`, `pkill tmux`, or `pkill claude`.
|
||||
- The session persists across disconnects — your work is safe.
|
||||
- Hooks may auto-format or validate after writes; unexpected tool behavior usually means a hook ran. Keep working.
|
||||
|
||||
## Ralph Loop (Codeman Autonomous Mode)
|
||||
|
||||
Start: `/ralph-loop:ralph-loop` · Cancel: `/ralph-loop:cancel-ralph` · Help: `/ralph-loop:help`
|
||||
|
||||
You are in a Ralph loop when the prompt contains a completion phrase (e.g. `<promise>COMPLETE</promise>`). While looping:
|
||||
|
||||
- Work incrementally: one sub-task at a time — implement, verify, commit, move on.
|
||||
- Fix failing tests and lint errors before starting the next task.
|
||||
- Check `git log --oneline -10` / `git diff HEAD~1` before retrying an approach that already failed.
|
||||
- Keep testing to ~20% of total effort; prioritize implementation. Don't refactor working code or add unrequested
|
||||
features as busy work.
|
||||
- If the prompt sets a minimum duration (e.g. "work for 4 hours"), record the start time (`date +%s`) and, when the
|
||||
primary tasks finish early, keep generating useful related work (edge cases, coverage, docs, cleanup, hardening)
|
||||
until the minimum time is reached.
|
||||
|
||||
Output the completion phrase ONLY when every requirement is verifiably done: all tests pass, lint is clean, the build
|
||||
succeeds, and changes are committed. Never output it early — and never withhold it for busy work once everything is
|
||||
genuinely done.
|
||||
|
||||
### RALPH_STATUS block (required)
|
||||
|
||||
End EVERY response during a Ralph loop with exactly this block — Codeman parses it to track the loop:
|
||||
|
||||
```
|
||||
---RALPH_STATUS---
|
||||
STATUS: IN_PROGRESS | COMPLETE | BLOCKED
|
||||
TASKS_COMPLETED_THIS_LOOP: <number>
|
||||
FILES_MODIFIED: <number>
|
||||
TESTS_STATUS: PASSING | FAILING | NOT_RUN
|
||||
WORK_TYPE: IMPLEMENTATION | TESTING | DOCUMENTATION | REFACTORING
|
||||
EXIT_SIGNAL: false | true
|
||||
RECOMMENDATION: <one line: what to do next>
|
||||
---END_RALPH_STATUS---
|
||||
```
|
||||
|
||||
- `EXIT_SIGNAL: true` only when ALL tasks are verifiably done — then also output the completion phrase.
|
||||
- `STATUS: BLOCKED` when you need human input; describe the blocker in RECOMMENDATION.
|
||||
- Never set `EXIT_SIGNAL: true` while tests are failing.
|
||||
|
||||
@@ -205,7 +205,12 @@ export function registerSecurityHeaders(app: FastifyInstance, https: boolean): v
|
||||
const scriptSrc =
|
||||
"script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net" + (gesture ? " 'wasm-unsafe-eval'" : '');
|
||||
const connectSrc = "connect-src 'self' wss://api.deepgram.com";
|
||||
const workerSrc = gesture ? "; worker-src 'self' blob:" : '';
|
||||
// blob: workers are needed unconditionally: terminal-ui's _safeYield tick
|
||||
// worker (throttling escape) is created from a Blob URL. Without this, every
|
||||
// page load logs a CSP violation and the worker leg of _safeYield is dead.
|
||||
// Risk is minimal — only same-origin scripts (already governed by script-src)
|
||||
// can construct blob workers.
|
||||
const workerSrc = "; worker-src 'self' blob:";
|
||||
const csp =
|
||||
`default-src 'self'; ${scriptSrc}; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; ` +
|
||||
`img-src 'self' data: blob:; ${connectSrc}; font-src 'self' https://cdn.jsdelivr.net; frame-ancestors 'self'${workerSrc}`;
|
||||
|
||||
@@ -378,7 +378,10 @@ Object.assign(CodemanApp.prototype, {
|
||||
prompt += `Output \`<promise>${config.completionPhrase}</promise>\` when done\n\n`;
|
||||
|
||||
prompt += '## If Stuck\n';
|
||||
prompt += 'Output `<promise>BLOCKED</promise>` with explanation';
|
||||
prompt += 'Output `<promise>BLOCKED</promise>` with explanation\n\n';
|
||||
|
||||
prompt += '## Status Reporting\n';
|
||||
prompt += '• End every response with a `RALPH_STATUS` block (parsed by Codeman)';
|
||||
|
||||
// Show preview with highlighting (escape first, then apply formatting)
|
||||
const escapedPrompt = escapeHtml(prompt);
|
||||
|
||||
@@ -839,7 +839,11 @@ Object.assign(CodemanApp.prototype, {
|
||||
|
||||
// Pattern 1: Commands with file paths (tail -f, cat, head, grep pattern, etc.)
|
||||
// Handles: tail -f /path, grep pattern /path, cat -n /path
|
||||
const cmdPattern = /(tail|cat|head|less|grep|watch|vim|nano)\s+(?:[^\s\/]*\s+)*(\/[^\s"'<>|;&\n\x00-\x1f]+)/g;
|
||||
// ⚠ The arg group must stay linear-time: `(?:[^\s\/]*\s+)*` (empty-matchable
|
||||
// token, unbounded) backtracks exponentially on lines with a trigger word
|
||||
// followed by multi-space runs (e.g. wrapped heredoc/table output) — froze
|
||||
// the whole tab on hover. Non-empty token + bounded reps is O(n).
|
||||
const cmdPattern = /\b(tail|cat|head|less|grep|watch|vim|nano)\s+(?:[^\s\/]+\s+){0,4}(\/[^\s"'<>|;&\n\x00-\x1f]+)/g;
|
||||
|
||||
// Pattern 2: Paths with common extensions
|
||||
const extPattern =
|
||||
|
||||
@@ -16,6 +16,7 @@ import { SseEvent } from '../sse-events.js';
|
||||
import { autoConfigureRalph, CASES_DIR, SETTINGS_PATH, findSessionOrFail, parseBody } from '../route-helpers.js';
|
||||
import { writeHooksConfig, stripCaseEnvKeys } from '../../hooks-config.js';
|
||||
import { generateClaudeMd } from '../../templates/claude-md.js';
|
||||
import { buildRalphLoopPrompt } from '../../prompts/index.js';
|
||||
import { getLifecycleLog } from '../../session-lifecycle-log.js';
|
||||
import type { SessionPort, EventPort, RespawnPort, ConfigPort, InfraPort } from '../ports/index.js';
|
||||
import { MAX_CONCURRENT_SESSIONS } from '../../config/map-limits.js';
|
||||
@@ -382,37 +383,12 @@ export function registerRalphRoutes(
|
||||
writeFileSync(fixPlanPath, planContent, 'utf-8');
|
||||
}
|
||||
|
||||
// Build full prompt
|
||||
const hasPlan = enabledItems.length > 0;
|
||||
let fullPrompt = taskDescription + '\n\n---\n\n';
|
||||
if (hasPlan) {
|
||||
fullPrompt += '## Task Plan\n\n';
|
||||
fullPrompt += 'A task plan has been written to `@fix_plan.md`. Use this to track progress:\n';
|
||||
fullPrompt += '- Reference the plan at the start of each iteration\n';
|
||||
fullPrompt += '- Update task checkboxes as you complete items\n';
|
||||
fullPrompt += '- Work through items in priority order (P0 > P1 > P2)\n\n';
|
||||
}
|
||||
fullPrompt += '## Iteration Protocol\n\n';
|
||||
fullPrompt += 'This is an autonomous loop. Files from previous iterations persist. On each iteration:\n';
|
||||
fullPrompt += '1. Check what work has already been done\n';
|
||||
fullPrompt += '2. Make incremental progress toward completion\n';
|
||||
fullPrompt += '3. Commit meaningful changes with descriptive messages\n\n';
|
||||
fullPrompt += '## Verification\n\n';
|
||||
fullPrompt += 'After each significant change:\n';
|
||||
fullPrompt += '- Run tests to verify (npm test, pytest, etc.)\n';
|
||||
fullPrompt += '- Check for type/lint errors if applicable\n';
|
||||
fullPrompt += '- If tests fail, read the error, fix it, and retry\n\n';
|
||||
fullPrompt += '## Completion Criteria\n\n';
|
||||
fullPrompt += `Output \`<promise>${completionPhrase}</promise>\` when ALL of the following are true:\n`;
|
||||
fullPrompt += '- All requirements from the task description are implemented\n';
|
||||
fullPrompt += '- All tests pass\n';
|
||||
fullPrompt += '- Changes are committed\n\n';
|
||||
fullPrompt += '## If Stuck\n\n';
|
||||
fullPrompt += 'If you encounter the same error for 3+ iterations:\n';
|
||||
fullPrompt += "1. Document what you've tried\n";
|
||||
fullPrompt += '2. Identify the specific blocker\n';
|
||||
fullPrompt += '3. Try an alternative approach\n';
|
||||
fullPrompt += '4. If truly blocked, output `<promise>BLOCKED</promise>` with an explanation\n';
|
||||
// Build full prompt (includes the RALPH_STATUS contract)
|
||||
const fullPrompt = buildRalphLoopPrompt({
|
||||
taskDescription,
|
||||
completionPhrase,
|
||||
hasPlan: enabledItems.length > 0,
|
||||
});
|
||||
|
||||
// Write prompt to file
|
||||
const promptPath = join(casePath, '@ralph_prompt.md');
|
||||
|
||||
Reference in New Issue
Block a user