Skip to content

Commit 9cd0bfc

Browse files
committed
add —no-verify and screenshot instructions
1 parent cd4dd61 commit 9cd0bfc

12 files changed

Lines changed: 119 additions & 537 deletions

File tree

general/skills/ralph/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
name: ralph
33
description: "Convert PRDs to prd.json format for the Ralph autonomous agent system. Use when you have an existing PRD and need to convert it to Ralph's JSON format. Triggers on: convert this prd, turn this into ralph format, create prd.json from this, ralph json."
4-
user-invokable: true
4+
user-invocable: true
55
---
66

77
# Ralph PRD Converter

planning/.claude-plugin/plugin.json

Lines changed: 0 additions & 9 deletions
This file was deleted.

planning/skills/plan/SKILL.md

Lines changed: 0 additions & 142 deletions
This file was deleted.

scripts/eternity-loop/eternity-loop-prompts/ralph-claude-md.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ You are an autonomous coding agent working on a software project.
1313
7. Update CLAUDE.md files if you discover reusable patterns (see below)
1414
8. If checks pass, commit ALL changes with message: `feat: [Story ID] - [Story Title]`
1515
9. Update the PRD to set `passes: true` for the completed story
16-
10. Append your progress to `progress.txt`
16+
10. Append your progress to `progress.txt`. When doing UI-related things and testing in browser or simulator tasks take screenshots of the results (one or max two per modified screen) and reference them in progress.txt
1717

1818
## Progress Report Format
1919

@@ -22,6 +22,7 @@ APPEND to progress.txt (never replace, always append):
2222
## [Date/Time] - [Story ID]
2323
- What was implemented
2424
- Files changed
25+
- References to screenshots taken (e.g. "See screenshot: progress-screenshots/story-id.png") with a brief description of what the screenshot shows
2526
- **Learnings for future iterations:**
2627
- Patterns discovered (e.g., "this codebase uses X for Y")
2728
- Gotchas encountered (e.g., "don't forget to update Z when changing W")

scripts/eternity-loop/git.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export async function checkoutBranch(workDir: string, branch: string): Promise<v
4242

4343
export async function pushBranch(workDir: string, branch?: string): Promise<void> {
4444
const branchName = branch ?? await getCurrentBranch(workDir);
45-
await $`git -C ${workDir} push -u origin ${branchName}`;
45+
await $`git -C ${workDir} push -u origin ${branchName} --no-verify`;
4646
}
4747

4848
export async function getCurrentBranch(workDir: string): Promise<string> {

scripts/eternity-loop/github/pr.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Octokit } from "@octokit/rest";
22
import { graphql } from "@octokit/graphql";
3+
import { join, basename } from "node:path";
34

45
export async function findPrByBranch(
56
octokit: Octokit,
@@ -287,3 +288,101 @@ export async function countWorkflowRunsSinceLastHumanInteraction(
287288
new Date(c.createdAt) > cutoff
288289
).length;
289290
}
291+
292+
/**
293+
* Extract screenshot file paths referenced in progress.txt content.
294+
* Looks for image file paths (relative or absolute) in the text.
295+
*/
296+
export function extractScreenshotPaths(progressContent: string, workDir: string): string[] {
297+
const paths: string[] = [];
298+
299+
// Match file paths ending in image extensions
300+
// Handles: ./path/to/file.png, path/to/file.png, /absolute/path/file.png
301+
// Also handles paths in markdown image syntax: ![alt](path.png)
302+
const pathPattern = /(?:!\[[^\]]*\]\()?([^\s\n\r"'()]+\.(?:png|jpg|jpeg|gif|webp|svg))(?:\))?/gi;
303+
304+
for (const match of progressContent.matchAll(pathPattern)) {
305+
const filePath = match[1];
306+
if (!filePath) continue;
307+
308+
// Resolve relative paths against workDir
309+
const resolved = filePath.startsWith("/") ? filePath : join(workDir, filePath);
310+
if (!paths.includes(resolved)) {
311+
paths.push(resolved);
312+
}
313+
}
314+
315+
return paths;
316+
}
317+
318+
/**
319+
* Get the relative path of a file within a git repo.
320+
*/
321+
async function getRelativePath(workDir: string, absolutePath: string): Promise<string> {
322+
// If path is already relative (starts within workDir), extract relative portion
323+
if (absolutePath.startsWith(workDir)) {
324+
return absolutePath.slice(workDir.length).replace(/^\//, "");
325+
}
326+
// Otherwise try git to resolve
327+
try {
328+
const result = await Bun.$`git -C ${workDir} ls-files --full-name ${absolutePath}`.text();
329+
return result.trim();
330+
} catch {
331+
return basename(absolutePath);
332+
}
333+
}
334+
335+
/**
336+
* Upload screenshots referenced in progress.txt to the PR as a comment.
337+
* Screenshots that are committed to the branch are referenced via raw GitHub URLs.
338+
* Screenshots not yet tracked are committed first, then referenced.
339+
*/
340+
export async function uploadProgressScreenshots(
341+
octokit: Octokit,
342+
owner: string,
343+
repo: string,
344+
prNumber: number,
345+
progressContent: string,
346+
workDir: string,
347+
branch: string,
348+
): Promise<void> {
349+
const screenshotPaths = extractScreenshotPaths(progressContent, workDir);
350+
if (screenshotPaths.length === 0) return;
351+
352+
const imageEntries: Array<{ name: string; url: string }> = [];
353+
354+
for (const filePath of screenshotPaths) {
355+
const file = Bun.file(filePath);
356+
if (!(await file.exists())) continue;
357+
358+
const relativePath = await getRelativePath(workDir, filePath);
359+
const name = basename(filePath);
360+
361+
// Check if file is tracked in git
362+
try {
363+
await Bun.$`git -C ${workDir} ls-files --error-unmatch ${filePath}`.quiet();
364+
} catch {
365+
// File not tracked - add and commit it
366+
try {
367+
await Bun.$`git -C ${workDir} add ${filePath}`.quiet();
368+
await Bun.$`git -C ${workDir} commit -m ${"chore: add screenshot " + name}`.quiet();
369+
} catch {
370+
continue; // Skip if we can't commit
371+
}
372+
}
373+
374+
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${relativePath}`;
375+
imageEntries.push({ name, url: rawUrl });
376+
}
377+
378+
if (imageEntries.length === 0) return;
379+
380+
const body = [
381+
`🤖 **eternity-loop bot:** Screenshots from this run:\n`,
382+
...imageEntries.map((entry, i) =>
383+
`### Screenshot ${i + 1}\n![${entry.name}](${entry.url})\n`
384+
),
385+
].join("\n");
386+
387+
await postPrComment(octokit, owner, repo, prNumber, body);
388+
}

scripts/eternity-loop/workflows/ci-fix.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { Issue, WorkflowContext } from "../types";
44
import { logDebug, logWorkflow, logPrdEntryCount } from "../logger";
55
import { checkoutBranch, pushBranch, getHeadSha } from "../git";
66
import { createGitHubClient, getRepoInfo } from "../github/client";
7-
import { findPrByBranch, postPrComment } from "../github/pr";
7+
import { findPrByBranch, postPrComment, uploadProgressScreenshots } from "../github/pr";
88
import { checkPrHasCiFailures, getCiFailureDetails, recordFixedSha } from "../github/ci";
99
import { readPrompt } from "../prompts";
1010
import { ClaudeCliRunner } from "../ai-runner";
@@ -127,6 +127,9 @@ export class CiFixWorkflow implements Workflow {
127127
`🤖 **eternity-loop bot:** CI fix applied.\n\n<details><summary>Progress log</summary>\n\n${progress}\n\n</details>`,
128128
);
129129

130+
// Upload screenshots referenced in progress.txt
131+
await uploadProgressScreenshots(octokit, owner, repo, issue.prNumber!, progress, ctx.workDir, issue.branchName);
132+
130133
logWorkflow("ci-fix", `[ci-fix] Finalized CI fix for ${issue.identifier} — https://github.com/${owner}/${repo}/pull/${issue.prNumber}`);
131134
}
132135
}

scripts/eternity-loop/workflows/new-feature.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { Issue, WorkflowContext } from "../types";
44
import { logDebug, logWorkflow, logPrdEntryCount } from "../logger";
55
import { ensureMainBranch, checkoutBranch, pushBranch } from "../git";
66
import { createGitHubClient, getRepoInfo } from "../github/client";
7-
import { createPr, postPrComment, findPrByBranch } from "../github/pr";
7+
import { createPr, postPrComment, findPrByBranch, uploadProgressScreenshots } from "../github/pr";
88
import { readPrompt } from "../prompts";
99
import { ClaudeCliRunner } from "../ai-runner";
1010
import { join } from "node:path";
@@ -95,6 +95,9 @@ export class NewFeatureWorkflow implements Workflow {
9595
`🤖 **eternity-loop bot:** Feature implementation complete.\n\n<details><summary>Progress log</summary>\n\n${progress}\n\n</details>`,
9696
);
9797

98+
// Upload screenshots referenced in progress.txt
99+
await uploadProgressScreenshots(octokit, owner, repo, pr.number, progress, ctx.workDir, issue.branchName);
100+
98101
logWorkflow("new-feature", `[new-feature] Created PR ${pr.url}`);
99102
} else {
100103
// Create draft PR via AI runner
@@ -109,6 +112,10 @@ export class NewFeatureWorkflow implements Workflow {
109112
await runner.run(fullPrompt, ctx.workDir);
110113

111114
const draftPr = await findPrByBranch(octokit, owner, repo, issue.branchName);
115+
if (draftPr) {
116+
const draftProgress = await Bun.file(join(ctx.ralphDir, "progress.txt")).text();
117+
await uploadProgressScreenshots(octokit, owner, repo, draftPr.number, draftProgress, ctx.workDir, issue.branchName);
118+
}
112119
const draftUrl = draftPr ? draftPr.url : `https://github.com/${owner}/${repo}`;
113120
logWorkflow("new-feature", `[new-feature] Created draft PR for ${issue.identifier}${draftUrl}`);
114121
}

scripts/eternity-loop/workflows/review.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { Issue, WorkflowContext } from "../types";
44
import { logDebug, logWorkflow, logPrdEntryCount } from "../logger";
55
import { checkoutBranch, pushBranch, getLatestCommitDate } from "../git";
66
import { createGitHubClient, getRepoInfo } from "../github/client";
7-
import { findPrByBranch, getPrComments, postPrComment, checkForNewHumanComments, countWorkflowRunsSinceLastHumanInteraction } from "../github/pr";
7+
import { findPrByBranch, getPrComments, postPrComment, checkForNewHumanComments, countWorkflowRunsSinceLastHumanInteraction, uploadProgressScreenshots } from "../github/pr";
88
import { readPrompt } from "../prompts";
99
import { ClaudeCliRunner } from "../ai-runner";
1010
import { join } from "node:path";
@@ -143,6 +143,9 @@ export class ReviewWorkflow implements Workflow {
143143
`🤖 **eternity-loop bot:** Review changes applied.\n\n<details><summary>Progress log</summary>\n\n${progress}\n\n</details>`,
144144
);
145145

146+
// Upload screenshots referenced in progress.txt
147+
await uploadProgressScreenshots(octokit, owner, repo, issue.prNumber!, progress, ctx.workDir, issue.branchName);
148+
146149
logWorkflow("review", `[review] Finalized review for ${issue.identifier} — https://github.com/${owner}/${repo}/pull/${issue.prNumber}`);
147150
}
148151
}

0 commit comments

Comments
 (0)