@canivel/ralph 0.2.0 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/.agents/ralph/PROMPT_build.md +126 -126
  2. package/.agents/ralph/agents.sh +17 -15
  3. package/.agents/ralph/config.sh +25 -25
  4. package/.agents/ralph/log-activity.sh +15 -15
  5. package/.agents/ralph/loop.sh +1027 -1001
  6. package/.agents/ralph/references/CONTEXT_ENGINEERING.md +126 -126
  7. package/.agents/ralph/references/GUARDRAILS.md +174 -174
  8. package/AGENTS.md +20 -20
  9. package/README.md +270 -266
  10. package/bin/ralph +766 -765
  11. package/diagram.svg +55 -55
  12. package/examples/commands.md +46 -46
  13. package/package.json +39 -39
  14. package/skills/commit/SKILL.md +219 -219
  15. package/skills/commit/references/commit_examples.md +292 -292
  16. package/skills/dev-browser/SKILL.md +211 -211
  17. package/skills/dev-browser/bun.lock +443 -443
  18. package/skills/dev-browser/package-lock.json +2988 -2988
  19. package/skills/dev-browser/package.json +31 -31
  20. package/skills/dev-browser/references/scraping.md +155 -155
  21. package/skills/dev-browser/scripts/start-relay.ts +32 -32
  22. package/skills/dev-browser/scripts/start-server.ts +117 -117
  23. package/skills/dev-browser/server.sh +24 -24
  24. package/skills/dev-browser/src/client.ts +474 -474
  25. package/skills/dev-browser/src/index.ts +287 -287
  26. package/skills/dev-browser/src/relay.ts +731 -731
  27. package/skills/dev-browser/src/snapshot/__tests__/snapshot.test.ts +223 -223
  28. package/skills/dev-browser/src/snapshot/browser-script.ts +877 -877
  29. package/skills/dev-browser/src/snapshot/index.ts +14 -14
  30. package/skills/dev-browser/src/snapshot/inject.ts +13 -13
  31. package/skills/dev-browser/src/types.ts +34 -34
  32. package/skills/dev-browser/tsconfig.json +36 -36
  33. package/skills/dev-browser/vitest.config.ts +12 -12
  34. package/skills/prd/SKILL.md +235 -235
  35. package/tests/agent-loops.mjs +79 -79
  36. package/tests/agent-ping.mjs +39 -39
  37. package/tests/audit.md +56 -56
  38. package/tests/cli-smoke.mjs +47 -47
  39. package/tests/real-agents.mjs +127 -127
@@ -1,39 +1,39 @@
1
- import { spawnSync } from "node:child_process";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
-
5
- const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
6
- const cliPath = path.join(repoRoot, "bin", "ralph");
7
-
8
- function run(cmd, args, options = {}) {
9
- const result = spawnSync(cmd, args, { stdio: "inherit", ...options });
10
- if (result.status !== 0) {
11
- console.error(`Command failed: ${cmd} ${args.join(" ")}`);
12
- process.exit(result.status ?? 1);
13
- }
14
- }
15
-
16
- const agents = [
17
- { name: "codex", bin: "codex" },
18
- { name: "claude", bin: "claude" },
19
- { name: "droid", bin: "droid" },
20
- { name: "opencode", bin: "opencode" },
21
- ];
22
- const runnable = [];
23
- const skipped = [];
24
- for (const agent of agents) {
25
- const check = spawnSync(`command -v ${agent.bin}`, { shell: true, stdio: "ignore" });
26
- if (check.status === 0) {
27
- runnable.push(agent.name);
28
- } else {
29
- skipped.push(agent.name);
30
- }
31
- }
32
- if (skipped.length) {
33
- console.log(`Skipping ping for missing agents: ${skipped.join(", ")}`);
34
- }
35
- for (const agent of runnable) {
36
- run(process.execPath, [cliPath, "ping", `--agent=${agent}`]);
37
- }
38
-
39
- console.log("Agent ping tests passed.");
1
+ import { spawnSync } from "node:child_process";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
6
+ const cliPath = path.join(repoRoot, "bin", "ralph");
7
+
8
+ function run(cmd, args, options = {}) {
9
+ const result = spawnSync(cmd, args, { stdio: "inherit", ...options });
10
+ if (result.status !== 0) {
11
+ console.error(`Command failed: ${cmd} ${args.join(" ")}`);
12
+ process.exit(result.status ?? 1);
13
+ }
14
+ }
15
+
16
+ const agents = [
17
+ { name: "codex", bin: "codex" },
18
+ { name: "claude", bin: "claude" },
19
+ { name: "droid", bin: "droid" },
20
+ { name: "opencode", bin: "opencode" },
21
+ ];
22
+ const runnable = [];
23
+ const skipped = [];
24
+ for (const agent of agents) {
25
+ const check = spawnSync(`command -v ${agent.bin}`, { shell: true, stdio: "ignore" });
26
+ if (check.status === 0) {
27
+ runnable.push(agent.name);
28
+ } else {
29
+ skipped.push(agent.name);
30
+ }
31
+ }
32
+ if (skipped.length) {
33
+ console.log(`Skipping ping for missing agents: ${skipped.join(", ")}`);
34
+ }
35
+ for (const agent of runnable) {
36
+ run(process.execPath, [cliPath, "ping", `--agent=${agent}`]);
37
+ }
38
+
39
+ console.log("Agent ping tests passed.");
package/tests/audit.md CHANGED
@@ -1,56 +1,56 @@
1
- # Ralph Test Coverage Audit
2
-
3
- ## Scope reviewed
4
- - `bin/ralph`
5
- - `.agents/ralph/loop.sh`
6
- - `.agents/ralph/PROMPT_build.md`
7
- - `.agents/ralph/log-activity.sh`
8
- - `.agents/ralph/references/*`
9
- - `skills/*`
10
- - `tests/*`
11
-
12
- ## Current test coverage
13
-
14
- ### CLI
15
- - `tests/cli-smoke.mjs`
16
- - Verifies `--help` output
17
- - Verifies `ralph prd ... --out` creates a PRD JSON file (dry-run)
18
- - Verifies `ralph overview --prd ...` creates an overview file
19
-
20
- ### Loop (dry-run)
21
- - `tests/agent-loops.mjs`
22
- - Runs `ralph build 1 --no-commit` with all agents using `RALPH_DRY_RUN=1`
23
- - Confirms loops execute and log to stdout without real agent execution
24
-
25
- ### Agent ping (fast, real)
26
- - `tests/agent-ping.mjs`
27
- - Runs `ralph ping` for codex/claude/droid
28
- - Verifies each agent responds with `<end>pong</end>`
29
-
30
- ### Real integration (new)
31
- - `tests/real-agents.mjs`
32
- - Creates a temp repo with a 2‑story JSON PRD and minimal AGENTS instructions
33
- - Runs `ralph build 2 --agent=<codex|claude|droid>`
34
- - Verifies:
35
- - PRD stories are all marked `done`
36
- - At least one git commit was created
37
- - Progress log exists
38
- - Cleans up the temp repo and logs after each agent run (pass or fail)
39
-
40
- ## Gaps identified
41
- - **Interactive installs**: `ralph install` and `ralph install --skills` require prompts and are not exercised in automated tests.
42
- - **Skill installation paths**: Local vs global skill locations are not validated in tests.
43
- - **Failure logging**: `.ralph/errors.log` contents are not asserted.
44
- - **Activity logging**: Activity log presence is not asserted (only implied via run completion).
45
- - **Guardrails**: No test verifies that guardrails are created/updated.
46
-
47
- ## Recommendations
48
- 1. Keep default tests deterministic (`npm test`) and provide a separate real‑agent test (`npm run test:real`).
49
- 2. Add a non‑interactive flag for skill install if you want it testable in CI.
50
- 3. Add assertions for `.ralph/activity.log` and `.ralph/guardrails.md` creation in `tests/real-agents.mjs`.
51
- 4. Consider adding coverage for PRD selection when multiple JSON files exist.
52
-
53
- ## How to run
54
- - **Deterministic:** `npm test`
55
- - **Fast real agent check:** `npm run test:ping`
56
- - **Real agents:** `npm run test:real` (requires codex/claude/droid installed and authenticated)
1
+ # Ralph Test Coverage Audit
2
+
3
+ ## Scope reviewed
4
+ - `bin/ralph`
5
+ - `.agents/ralph/loop.sh`
6
+ - `.agents/ralph/PROMPT_build.md`
7
+ - `.agents/ralph/log-activity.sh`
8
+ - `.agents/ralph/references/*`
9
+ - `skills/*`
10
+ - `tests/*`
11
+
12
+ ## Current test coverage
13
+
14
+ ### CLI
15
+ - `tests/cli-smoke.mjs`
16
+ - Verifies `--help` output
17
+ - Verifies `ralph prd ... --out` creates a PRD JSON file (dry-run)
18
+ - Verifies `ralph overview --prd ...` creates an overview file
19
+
20
+ ### Loop (dry-run)
21
+ - `tests/agent-loops.mjs`
22
+ - Runs `ralph build 1 --no-commit` with all agents using `RALPH_DRY_RUN=1`
23
+ - Confirms loops execute and log to stdout without real agent execution
24
+
25
+ ### Agent ping (fast, real)
26
+ - `tests/agent-ping.mjs`
27
+ - Runs `ralph ping` for codex/claude/droid
28
+ - Verifies each agent responds with `<end>pong</end>`
29
+
30
+ ### Real integration (new)
31
+ - `tests/real-agents.mjs`
32
+ - Creates a temp repo with a 2‑story JSON PRD and minimal AGENTS instructions
33
+ - Runs `ralph build 2 --agent=<codex|claude|droid>`
34
+ - Verifies:
35
+ - PRD stories are all marked `done`
36
+ - At least one git commit was created
37
+ - Progress log exists
38
+ - Cleans up the temp repo and logs after each agent run (pass or fail)
39
+
40
+ ## Gaps identified
41
+ - **Interactive installs**: `ralph install` and `ralph install --skills` require prompts and are not exercised in automated tests.
42
+ - **Skill installation paths**: Local vs global skill locations are not validated in tests.
43
+ - **Failure logging**: `.ralph/errors.log` contents are not asserted.
44
+ - **Activity logging**: Activity log presence is not asserted (only implied via run completion).
45
+ - **Guardrails**: No test verifies that guardrails are created/updated.
46
+
47
+ ## Recommendations
48
+ 1. Keep default tests deterministic (`npm test`) and provide a separate real‑agent test (`npm run test:real`).
49
+ 2. Add a non‑interactive flag for skill install if you want it testable in CI.
50
+ 3. Add assertions for `.ralph/activity.log` and `.ralph/guardrails.md` creation in `tests/real-agents.mjs`.
51
+ 4. Consider adding coverage for PRD selection when multiple JSON files exist.
52
+
53
+ ## How to run
54
+ - **Deterministic:** `npm test`
55
+ - **Fast real agent check:** `npm run test:ping`
56
+ - **Real agents:** `npm run test:real` (requires codex/claude/droid installed and authenticated)
@@ -1,47 +1,47 @@
1
- import { spawnSync } from "node:child_process";
2
- import { mkdtempSync, existsSync, rmSync } from "node:fs";
3
- import { tmpdir } from "node:os";
4
- import path from "node:path";
5
- import { fileURLToPath } from "node:url";
6
-
7
- function run(cmd, args, options = {}) {
8
- const result = spawnSync(cmd, args, { stdio: "inherit", ...options });
9
- if (result.status !== 0) {
10
- console.error(`Command failed: ${cmd} ${args.join(" ")}`);
11
- process.exit(result.status ?? 1);
12
- }
13
- }
14
-
15
- const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
16
- const cliPath = path.join(repoRoot, "bin", "ralph");
17
-
18
- run(process.execPath, [cliPath, "--help"]);
19
-
20
- const projectRoot = mkdtempSync(path.join(tmpdir(), "ralph-cli-"));
21
- try {
22
- const outPath = path.join(projectRoot, "prd.json");
23
- run(process.execPath, [cliPath, "prd", "Smoke test PRD", "--out", outPath], {
24
- cwd: projectRoot,
25
- env: { ...process.env, RALPH_DRY_RUN: "1" },
26
- });
27
-
28
- if (!existsSync(outPath)) {
29
- console.error("PRD smoke test failed: output not created.");
30
- process.exit(1);
31
- }
32
-
33
- run(process.execPath, [cliPath, "overview", "--prd", outPath], {
34
- cwd: projectRoot,
35
- env: { ...process.env },
36
- });
37
-
38
- const overviewPath = outPath.replace(/\.json$/i, ".overview.md");
39
- if (!existsSync(overviewPath)) {
40
- console.error("Overview smoke test failed: output not created.");
41
- process.exit(1);
42
- }
43
- } finally {
44
- rmSync(projectRoot, { recursive: true, force: true });
45
- }
46
-
47
- console.log("CLI smoke test passed.");
1
+ import { spawnSync } from "node:child_process";
2
+ import { mkdtempSync, existsSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ function run(cmd, args, options = {}) {
8
+ const result = spawnSync(cmd, args, { stdio: "inherit", ...options });
9
+ if (result.status !== 0) {
10
+ console.error(`Command failed: ${cmd} ${args.join(" ")}`);
11
+ process.exit(result.status ?? 1);
12
+ }
13
+ }
14
+
15
+ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
16
+ const cliPath = path.join(repoRoot, "bin", "ralph");
17
+
18
+ run(process.execPath, [cliPath, "--help"]);
19
+
20
+ const projectRoot = mkdtempSync(path.join(tmpdir(), "ralph-cli-"));
21
+ try {
22
+ const outPath = path.join(projectRoot, "prd.json");
23
+ run(process.execPath, [cliPath, "prd", "Smoke test PRD", "--out", outPath], {
24
+ cwd: projectRoot,
25
+ env: { ...process.env, RALPH_DRY_RUN: "1" },
26
+ });
27
+
28
+ if (!existsSync(outPath)) {
29
+ console.error("PRD smoke test failed: output not created.");
30
+ process.exit(1);
31
+ }
32
+
33
+ run(process.execPath, [cliPath, "overview", "--prd", outPath], {
34
+ cwd: projectRoot,
35
+ env: { ...process.env },
36
+ });
37
+
38
+ const overviewPath = outPath.replace(/\.json$/i, ".overview.md");
39
+ if (!existsSync(overviewPath)) {
40
+ console.error("Overview smoke test failed: output not created.");
41
+ process.exit(1);
42
+ }
43
+ } finally {
44
+ rmSync(projectRoot, { recursive: true, force: true });
45
+ }
46
+
47
+ console.log("CLI smoke test passed.");
@@ -1,127 +1,127 @@
1
- import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync, rmSync } from "node:fs";
2
- import { tmpdir } from "node:os";
3
- import path from "node:path";
4
- import { spawnSync } from "node:child_process";
5
- import { fileURLToPath } from "node:url";
6
-
7
- const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
8
- const cliPath = path.join(repoRoot, "bin", "ralph");
9
-
10
- function run(cmd, args, options = {}) {
11
- const result = spawnSync(cmd, args, { stdio: "inherit", ...options });
12
- if (result.status !== 0) {
13
- throw new Error(`Command failed: ${cmd} ${args.join(" ")}`);
14
- }
15
- }
16
-
17
- function setupTempProject() {
18
- const base = mkdtempSync(path.join(tmpdir(), "ralph-real-"));
19
- mkdirSync(path.join(base, ".agents", "tasks"), { recursive: true });
20
- mkdirSync(path.join(base, ".ralph"), { recursive: true });
21
-
22
- const prd = {
23
- version: 1,
24
- project: "Real Agent Smoke Test",
25
- qualityGates: [],
26
- stories: [
27
- {
28
- id: "US-001",
29
- title: "Create baseline file",
30
- status: "open",
31
- dependsOn: [],
32
- description:
33
- "As a user, I want a baseline file so the repo has an artifact.",
34
- acceptanceCriteria: [
35
- "File \"docs/US-001.txt\" exists with the exact text \"US-001 complete\"",
36
- "Example: open docs/US-001.txt -> \"US-001 complete\"",
37
- "Negative case: missing file -> failure",
38
- ],
39
- },
40
- {
41
- id: "US-002",
42
- title: "Add second artifact",
43
- status: "open",
44
- dependsOn: ["US-001"],
45
- description:
46
- "As a user, I want a second artifact to verify multi-story iteration.",
47
- acceptanceCriteria: [
48
- "File \"docs/US-002.txt\" exists with the exact text \"US-002 complete\"",
49
- "Example: open docs/US-002.txt -> \"US-002 complete\"",
50
- "Negative case: missing file -> failure",
51
- ],
52
- },
53
- ],
54
- };
55
-
56
- writeFileSync(
57
- path.join(base, ".agents", "tasks", "prd.json"),
58
- `${JSON.stringify(prd, null, 2)}\n`,
59
- );
60
-
61
- const agents = `# AGENTS\n\n## Validation\n- For this repo, verify changes with shell checks only.\n- Use commands like: test -f <file>, grep -q \"text\" <file>\n- Do NOT run package manager tests.\n`;
62
-
63
- writeFileSync(path.join(base, "AGENTS.md"), agents);
64
-
65
- run("git", ["init"], { cwd: base });
66
- run("git", ["config", "user.email", "ralph@example.com"], { cwd: base });
67
- run("git", ["config", "user.name", "ralph"], { cwd: base });
68
-
69
- return base;
70
- }
71
-
72
- function assertAllStoriesComplete(prdPath) {
73
- const prd = JSON.parse(readFileSync(prdPath, "utf-8"));
74
- const stories = Array.isArray(prd.stories) ? prd.stories : [];
75
- const remaining = stories.filter(
76
- (story) => String(story.status || "open").toLowerCase() !== "done",
77
- );
78
- if (remaining.length > 0) {
79
- throw new Error(
80
- `Some stories remain incomplete: ${remaining.map((s) => s.id).join(", ")}`,
81
- );
82
- }
83
- }
84
-
85
- function assertCommitted(cwd) {
86
- const result = spawnSync("git", ["rev-list", "--count", "HEAD"], { cwd, encoding: "utf-8" });
87
- if (result.status !== 0) {
88
- throw new Error("Failed to read git history.");
89
- }
90
- const count = Number(result.stdout.trim() || "0");
91
- if (count < 1) {
92
- throw new Error("Expected at least one commit, found none.");
93
- }
94
- }
95
-
96
- function runForAgent(agent) {
97
- console.log(`\n=== Real agent test: ${agent} ===`);
98
- const projectRoot = setupTempProject();
99
- const env = { ...process.env };
100
-
101
- try {
102
- run(process.execPath, [cliPath, "build", "2", `--agent=${agent}`], {
103
- cwd: projectRoot,
104
- env,
105
- });
106
-
107
- const prdPath = path.join(projectRoot, ".agents", "tasks", "prd.json");
108
- assertAllStoriesComplete(prdPath);
109
- assertCommitted(projectRoot);
110
-
111
- const progressPath = path.join(projectRoot, ".ralph", "progress.md");
112
- if (!existsSync(progressPath)) {
113
- throw new Error("Progress log not created.");
114
- }
115
-
116
- console.log(`Agent ${agent} passed.`);
117
- } finally {
118
- rmSync(projectRoot, { recursive: true, force: true });
119
- }
120
- }
121
-
122
- const agents = ["codex", "claude", "droid"];
123
- for (const agent of agents) {
124
- runForAgent(agent);
125
- }
126
-
127
- console.log("Real agent integration tests passed.");
1
+ import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import path from "node:path";
4
+ import { spawnSync } from "node:child_process";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
8
+ const cliPath = path.join(repoRoot, "bin", "ralph");
9
+
10
+ function run(cmd, args, options = {}) {
11
+ const result = spawnSync(cmd, args, { stdio: "inherit", ...options });
12
+ if (result.status !== 0) {
13
+ throw new Error(`Command failed: ${cmd} ${args.join(" ")}`);
14
+ }
15
+ }
16
+
17
+ function setupTempProject() {
18
+ const base = mkdtempSync(path.join(tmpdir(), "ralph-real-"));
19
+ mkdirSync(path.join(base, ".agents", "tasks"), { recursive: true });
20
+ mkdirSync(path.join(base, ".ralph"), { recursive: true });
21
+
22
+ const prd = {
23
+ version: 1,
24
+ project: "Real Agent Smoke Test",
25
+ qualityGates: [],
26
+ stories: [
27
+ {
28
+ id: "US-001",
29
+ title: "Create baseline file",
30
+ status: "open",
31
+ dependsOn: [],
32
+ description:
33
+ "As a user, I want a baseline file so the repo has an artifact.",
34
+ acceptanceCriteria: [
35
+ "File \"docs/US-001.txt\" exists with the exact text \"US-001 complete\"",
36
+ "Example: open docs/US-001.txt -> \"US-001 complete\"",
37
+ "Negative case: missing file -> failure",
38
+ ],
39
+ },
40
+ {
41
+ id: "US-002",
42
+ title: "Add second artifact",
43
+ status: "open",
44
+ dependsOn: ["US-001"],
45
+ description:
46
+ "As a user, I want a second artifact to verify multi-story iteration.",
47
+ acceptanceCriteria: [
48
+ "File \"docs/US-002.txt\" exists with the exact text \"US-002 complete\"",
49
+ "Example: open docs/US-002.txt -> \"US-002 complete\"",
50
+ "Negative case: missing file -> failure",
51
+ ],
52
+ },
53
+ ],
54
+ };
55
+
56
+ writeFileSync(
57
+ path.join(base, ".agents", "tasks", "prd.json"),
58
+ `${JSON.stringify(prd, null, 2)}\n`,
59
+ );
60
+
61
+ const agents = `# AGENTS\n\n## Validation\n- For this repo, verify changes with shell checks only.\n- Use commands like: test -f <file>, grep -q \"text\" <file>\n- Do NOT run package manager tests.\n`;
62
+
63
+ writeFileSync(path.join(base, "AGENTS.md"), agents);
64
+
65
+ run("git", ["init"], { cwd: base });
66
+ run("git", ["config", "user.email", "ralph@example.com"], { cwd: base });
67
+ run("git", ["config", "user.name", "ralph"], { cwd: base });
68
+
69
+ return base;
70
+ }
71
+
72
+ function assertAllStoriesComplete(prdPath) {
73
+ const prd = JSON.parse(readFileSync(prdPath, "utf-8"));
74
+ const stories = Array.isArray(prd.stories) ? prd.stories : [];
75
+ const remaining = stories.filter(
76
+ (story) => String(story.status || "open").toLowerCase() !== "done",
77
+ );
78
+ if (remaining.length > 0) {
79
+ throw new Error(
80
+ `Some stories remain incomplete: ${remaining.map((s) => s.id).join(", ")}`,
81
+ );
82
+ }
83
+ }
84
+
85
+ function assertCommitted(cwd) {
86
+ const result = spawnSync("git", ["rev-list", "--count", "HEAD"], { cwd, encoding: "utf-8" });
87
+ if (result.status !== 0) {
88
+ throw new Error("Failed to read git history.");
89
+ }
90
+ const count = Number(result.stdout.trim() || "0");
91
+ if (count < 1) {
92
+ throw new Error("Expected at least one commit, found none.");
93
+ }
94
+ }
95
+
96
+ function runForAgent(agent) {
97
+ console.log(`\n=== Real agent test: ${agent} ===`);
98
+ const projectRoot = setupTempProject();
99
+ const env = { ...process.env };
100
+
101
+ try {
102
+ run(process.execPath, [cliPath, "build", "2", `--agent=${agent}`], {
103
+ cwd: projectRoot,
104
+ env,
105
+ });
106
+
107
+ const prdPath = path.join(projectRoot, ".agents", "tasks", "prd.json");
108
+ assertAllStoriesComplete(prdPath);
109
+ assertCommitted(projectRoot);
110
+
111
+ const progressPath = path.join(projectRoot, ".ralph", "progress.md");
112
+ if (!existsSync(progressPath)) {
113
+ throw new Error("Progress log not created.");
114
+ }
115
+
116
+ console.log(`Agent ${agent} passed.`);
117
+ } finally {
118
+ rmSync(projectRoot, { recursive: true, force: true });
119
+ }
120
+ }
121
+
122
+ const agents = ["codex", "claude", "droid"];
123
+ for (const agent of agents) {
124
+ runForAgent(agent);
125
+ }
126
+
127
+ console.log("Real agent integration tests passed.");