@sureshsankaran/ralph-wiggum 0.1.8 → 0.1.11

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.
@@ -4,78 +4,11 @@ description: "Start Ralph Wiggum loop in current session"
4
4
 
5
5
  # Ralph Loop Command
6
6
 
7
- !`export RALPH_ARGS='$ARGUMENTS' && node -e "
8
- var args = process.env.RALPH_ARGS || '';
9
- var fs = require('fs');
10
- var path = require('path');
11
-
12
- // Unescape the CLI escaping - remove outer quotes and unescape inner quotes
13
- args = args.trim();
14
- if ((args.startsWith('\"') && args.endsWith('\"')) || (args.startsWith(\"'\") && args.endsWith(\"'\"))) {
15
- args = args.slice(1, -1);
16
- }
17
- args = args.replace(/\\\\\"/g, '\"');
18
-
19
- var maxIterations = 0;
20
- var completionPromise = 'null';
21
- var prompt = args;
22
-
23
- var maxMatch = args.match(/--max-iterations\s+(\d+)/);
24
- if (maxMatch) {
25
- maxIterations = parseInt(maxMatch[1], 10);
26
- prompt = prompt.replace(/--max-iterations\s+\d+/, '');
27
- }
28
-
29
- var cpMatch = args.match(/--completion-promise\s+\"([^\"]+)\"/);
30
- if (cpMatch) {
31
- completionPromise = cpMatch[1];
32
- prompt = prompt.replace(/--completion-promise\s+\"[^\"]+\"/, '');
33
- } else {
34
- var cpMatch2 = args.match(/--completion-promise\s+(\S+)/);
35
- if (cpMatch2) {
36
- completionPromise = cpMatch2[1];
37
- prompt = prompt.replace(/--completion-promise\s+\S+/, '');
38
- }
39
- }
40
-
41
- prompt = prompt.trim().replace(/^\"|\"$/g, '');
42
-
43
- if (!prompt) {
44
- console.log('Error: No prompt provided.');
45
- console.log('Usage: /ralph-loop \"<prompt>\" [--max-iterations N] [--completion-promise TEXT]');
46
- process.exit(1);
47
- }
48
-
49
- var configDir = process.env.XDG_CONFIG_HOME || path.join(require('os').homedir(), '.config');
50
- var dir = path.join(configDir, 'opencode', 'state');
51
- fs.mkdirSync(dir, { recursive: true });
52
-
53
- var cpYaml = completionPromise === 'null' ? 'null' : '\"' + completionPromise + '\"';
54
- var lines = [
55
- '---',
56
- 'active: true',
57
- 'iteration: 1',
58
- 'max_iterations: ' + maxIterations,
59
- 'completion_promise: ' + cpYaml,
60
- 'started_at: \"' + new Date().toISOString() + '\"',
61
- '---',
62
- '',
63
- prompt
64
- ];
65
-
66
- fs.writeFileSync(path.join(dir, 'ralph-loop.local.md'), lines.join('\n') + '\n');
67
-
68
- console.log('Ralph loop activated!');
69
- console.log('Iteration: 1');
70
- console.log('Max iterations: ' + (maxIterations > 0 ? maxIterations : 'unlimited'));
71
- console.log('Completion promise: ' + (completionPromise !== 'null' ? completionPromise : 'none'));
72
- console.log('');
73
- console.log(prompt);
74
- "`
7
+ !`node ~/.config/opencode/scripts/ralph-wiggum/setup-loop.js $ARGUMENTS`
75
8
 
76
9
  Please work on the task described above. The Ralph loop is now active.
77
10
 
78
- When the session becomes idle, the session.idle hook will feed the SAME PROMPT back to you for the next iteration. You'll see your previous work in files and git history, allowing you to iterate and improve.
11
+ When the session becomes idle, the session.stop hook will feed the SAME PROMPT back to you for the next iteration. You'll see your previous work in files and git history, allowing you to iterate and improve.
79
12
 
80
13
  CRITICAL RULE: If a completion promise is set, you may ONLY output it when the statement is completely and unequivocally TRUE. Do not output false promises to escape the loop, even if you think you're stuck or should exit for other reasons. The loop is designed to continue until genuine completion.
81
14
 
package/dist/index.js CHANGED
@@ -67,7 +67,8 @@ export const RalphWiggumPlugin = async ({ client }) => {
67
67
  const stateFilePath = getStateFilePath();
68
68
  // In-memory state to prevent double-processing
69
69
  let completionDetected = false;
70
- let lastProcessedIteration = -1;
70
+ let pendingIteration = -1; // Track iteration we're currently processing/waiting for
71
+ let lastAssistantId = ""; // Track last assistant message to detect new responses
71
72
  // Cast to extended hooks type that includes experimental.session.stop
72
73
  const hooks = {
73
74
  // Stop hook: called when main session loop is about to exit
@@ -89,6 +90,14 @@ export const RalphWiggumPlugin = async ({ client }) => {
89
90
  .find((m) => m.info.role === "assistant" && m.parts.some((p) => p.type === "text"));
90
91
  if (!lastAssistant)
91
92
  return;
93
+ // Prevent double-triggering: if we already sent a prompt for this iteration
94
+ // and haven't seen a new assistant message, skip
95
+ if (pendingIteration === state.iteration && lastAssistant.info.id === lastAssistantId) {
96
+ output.decision = "block"; // Still block, we're waiting for AI to respond
97
+ return;
98
+ }
99
+ // Update tracking
100
+ lastAssistantId = lastAssistant.info.id;
92
101
  const textParts = lastAssistant.parts.filter((p) => p.type === "text");
93
102
  const fullText = textParts.map((p) => p.text).join("\n");
94
103
  // Check completion promise
@@ -113,11 +122,8 @@ export const RalphWiggumPlugin = async ({ client }) => {
113
122
  catch { }
114
123
  return;
115
124
  }
116
- // Check if we've already advanced this iteration
117
- if (state.iteration <= lastProcessedIteration)
118
- return;
119
125
  const nextIteration = state.iteration + 1;
120
- lastProcessedIteration = nextIteration;
126
+ pendingIteration = nextIteration;
121
127
  // Update state file with new iteration
122
128
  const updated = content.replace(/^iteration: \d+$/m, `iteration: ${nextIteration}`);
123
129
  writeFileSync(stateFilePath, updated);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sureshsankaran/ralph-wiggum",
3
- "version": "0.1.8",
3
+ "version": "0.1.11",
4
4
  "description": "Ralph Wiggum iterative AI development plugin for OpenCode - continuously loops the same prompt until task completion",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -8,12 +8,16 @@ import { fileURLToPath } from "node:url"
8
8
  const __dirname = dirname(fileURLToPath(import.meta.url))
9
9
  const packageRoot = join(__dirname, "..")
10
10
 
11
- const configDir = join(homedir(), ".config", "opencode", "command")
12
-
13
- // Create command directory if it doesn't exist
14
- if (!existsSync(configDir)) {
15
- mkdirSync(configDir, { recursive: true })
16
- console.log(`Created directory: ${configDir}`)
11
+ const configBase = join(homedir(), ".config", "opencode")
12
+ const commandDir = join(configBase, "command")
13
+ const scriptsDir = join(configBase, "scripts", "ralph-wiggum")
14
+
15
+ // Create directories if they don't exist
16
+ for (const dir of [commandDir, scriptsDir]) {
17
+ if (!existsSync(dir)) {
18
+ mkdirSync(dir, { recursive: true })
19
+ console.log(`Created directory: ${dir}`)
20
+ }
17
21
  }
18
22
 
19
23
  // Copy command files
@@ -22,7 +26,7 @@ const commandsDir = join(packageRoot, "commands")
22
26
 
23
27
  for (const cmd of commands) {
24
28
  const src = join(commandsDir, cmd)
25
- const dest = join(configDir, cmd)
29
+ const dest = join(commandDir, cmd)
26
30
 
27
31
  if (existsSync(src)) {
28
32
  copyFileSync(src, dest)
@@ -32,6 +36,17 @@ for (const cmd of commands) {
32
36
  }
33
37
  }
34
38
 
39
+ // Copy setup script
40
+ const setupSrc = join(packageRoot, "scripts", "setup-loop.js")
41
+ const setupDest = join(scriptsDir, "setup-loop.js")
42
+
43
+ if (existsSync(setupSrc)) {
44
+ copyFileSync(setupSrc, setupDest)
45
+ console.log(`Installed script: setup-loop.js`)
46
+ } else {
47
+ console.warn(`Warning: Script file not found: ${setupSrc}`)
48
+ }
49
+
35
50
  console.log("")
36
51
  console.log("Ralph Wiggum commands installed!")
37
52
  console.log("")
@@ -39,7 +54,7 @@ console.log("Next step: Add the plugin to your opencode config:")
39
54
  console.log("")
40
55
  console.log(" In opencode.json:")
41
56
  console.log(" {")
42
- console.log(' "plugin": ["opencode-ralph-wiggum"]')
57
+ console.log(' "plugin": ["@sureshsankaran/ralph-wiggum"]')
43
58
  console.log(" }")
44
59
  console.log("")
45
60
  console.log("Usage:")
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Ralph Loop Setup Script
4
+ *
5
+ * Called by the ralph-loop command template to initialize the loop state.
6
+ * Arguments are passed directly to this script (not via shell substitution).
7
+ */
8
+
9
+ const fs = require("fs")
10
+ const path = require("path")
11
+ const os = require("os")
12
+
13
+ // Get arguments from command line (skip node and script name)
14
+ const rawArgs = process.argv.slice(2)
15
+
16
+ // Parse arguments
17
+ let maxIterations = 0
18
+ let completionPromise = null
19
+ let promptParts = []
20
+
21
+ for (let i = 0; i < rawArgs.length; i++) {
22
+ const arg = rawArgs[i]
23
+
24
+ if (arg === "--max-iterations" && i + 1 < rawArgs.length) {
25
+ maxIterations = parseInt(rawArgs[i + 1], 10) || 0
26
+ i++ // skip next arg
27
+ } else if (arg === "--completion-promise" && i + 1 < rawArgs.length) {
28
+ completionPromise = rawArgs[i + 1]
29
+ i++ // skip next arg
30
+ } else if (!arg.startsWith("--")) {
31
+ promptParts.push(arg)
32
+ }
33
+ }
34
+
35
+ const prompt = promptParts.join(" ").trim()
36
+
37
+ if (!prompt) {
38
+ console.log("Error: No prompt provided.")
39
+ console.log('Usage: --command ralph-loop "<prompt>" --max-iterations N --completion-promise TEXT')
40
+ process.exit(1)
41
+ }
42
+
43
+ // Determine state directory
44
+ const configDir = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config")
45
+ const stateDir = path.join(configDir, "opencode", "state")
46
+ fs.mkdirSync(stateDir, { recursive: true })
47
+
48
+ // Build state file content
49
+ const cpYaml = completionPromise ? `"${completionPromise}"` : "null"
50
+ const lines = [
51
+ "---",
52
+ "active: true",
53
+ "iteration: 1",
54
+ `max_iterations: ${maxIterations}`,
55
+ `completion_promise: ${cpYaml}`,
56
+ `started_at: "${new Date().toISOString()}"`,
57
+ "---",
58
+ "",
59
+ prompt,
60
+ ]
61
+
62
+ const stateFilePath = path.join(stateDir, "ralph-loop.local.md")
63
+ fs.writeFileSync(stateFilePath, lines.join("\n") + "\n")
64
+
65
+ console.log("Ralph loop activated!")
66
+ console.log(`Iteration: 1`)
67
+ console.log(`Max iterations: ${maxIterations > 0 ? maxIterations : "unlimited"}`)
68
+ console.log(`Completion promise: ${completionPromise || "none"}`)
69
+ console.log("")
70
+ console.log(prompt)